├── .gitignore ├── CMakeLists.txt ├── README.md ├── examples ├── exception_handling_example.cpp └── sigsegv_handling_example.cpp ├── include ├── app_info.hpp ├── core_dump.hpp ├── crashser.h ├── dump_server_connector.hpp ├── json_serializer.hpp ├── os_info.hpp └── stack_trace.hpp └── sources ├── dump_server_connector.cpp └── json_serializer.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | 3 | protobuf/* 4 | 5 | bin/* 6 | build/* 7 | cmake-build-debug/* 8 | cmake-build-release/* 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | 3 | project(crasher_cpp_client) 4 | 5 | set(CMAKE_CXX_STANDARD 11) 6 | 7 | set(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}) 8 | set(PROJECT_BIN_DIR ${CMAKE_CURRENT_SOURCE_DIR}/bin) 9 | set(PROJECT_BUILD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/build) 10 | set(PROJECT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) 11 | set(PROJECT_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sources) 12 | set(PROJECT_EXAMPLES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/examples) 13 | 14 | option(BUILD_EXAMPLES "Build examples" ON) 15 | option(BUILD_STATIC_LIBRARY "Build static or dynamic library" OFF) 16 | 17 | find_package(Boost REQUIRED) 18 | 19 | include_directories(${Boost_INCLUDE_DIRS} ${PROJECT_PROTOCOL_OUTPUT_DIR}) 20 | file(GLOB LIBRARY_SOURCE_FILES "sources/*.cpp") 21 | 22 | if(BUILD_STATIC_LIBRARY) 23 | add_library(${PROJECT_NAME} STATIC ${LIBRARY_SOURCE_FILES}) 24 | set_target_properties(${PROJECT_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BIN_DIR}) 25 | set_target_properties(${PROJECT_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_BIN_DIR}) 26 | set_target_properties(${PROJECT_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${PROJECT_BIN_DIR}) 27 | else() 28 | add_library(${PROJECT_NAME} SHARED ${LIBRARY_SOURCE_FILES}) 29 | set_target_properties(${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BIN_DIR}) 30 | set_target_properties(${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_DEBUG ${PROJECT_BIN_DIR}) 31 | set_target_properties(${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_RELEASE ${PROJECT_BIN_DIR}) 32 | endif() 33 | 34 | target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES}) 35 | if(CMAKE_GENERATOR MATCHES "Visual Studio") 36 | set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${PROJECT_BIN_DIR}) 37 | endif() 38 | 39 | if(BUILD_EXAMPLES) 40 | include_directories(${PROJECT_INCLUDE_DIR}) 41 | set(EXAMPLES sigsegv_handling_example exception_handling_example) 42 | foreach(EXAMPLE ${EXAMPLES}) 43 | add_executable(${EXAMPLE} examples/${EXAMPLE}.cpp) 44 | target_link_libraries(${EXAMPLE} ${Boost_LIBRARIES} ${PROJECT_NAME}) 45 | set_target_properties(${EXAMPLE} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BIN_DIR}) 46 | set_target_properties(${EXAMPLE} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_BIN_DIR}) 47 | set_target_properties(${EXAMPLE} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_BIN_DIR}) 48 | if(CMAKE_GENERATOR MATCHES "Visual Studio") 49 | set_target_properties(${EXAMPLE} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${PROJECT_BIN_DIR}) 50 | endif() 51 | endforeach() 52 | endif() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++ library for Crasher 2 | 3 | [Crashser](https://github.com/VladimirBalun/Crashser) - 4 | open source dump/crash server for different programming 5 | languages (used for crash analysis in various applications). 6 | This library is crossplatfrom (now only Windows, Linux, OSX) 7 | implementation C++ client for Crasher dump/crash server. 8 | 9 | ## How to build library 10 | In order to build the library, you will need 11 | the following installed packages on your machine: 12 | 13 | - **boost** 14 | 15 | After that you can use the following commands: 16 | 17 | cd crasher-cpp-client 18 | mkdir build 19 | cd build 20 | cmake .. 21 | 22 | ## Additional build options 23 | You can choose target library type (static 24 | or dynamic), disabling or enabling examples 25 | with the following options for cmake: 26 | 27 | cmake .. -DBUILD_STATIC_LIBRARY=ON -DBUILD_EXAMPLES=ON 28 | 29 | ## Examples 30 | 31 | Segmentation Faults and std::terminate calls sometimes 32 | happen in programs. Programmers usually wish to get as 33 | much information as possible on such incidents, 34 | so having a stacktrace will significantly improve 35 | debugging and fixing. So, we can you Crashser library for these 36 | purposes: 37 | 38 | ```cpp 39 | #include "crashser.h" 40 | 41 | void my_signal_handler(int sig_number) 42 | { 43 | try { 44 | // OSInfo name is determined automatically, but you can override it like below 45 | const Crashser::OSInfo os_info("OSX", "11.2.2"); 46 | const Crashser::AppInfo app_info("test_example_app", "0.0.1"); 47 | 48 | // Getting stack trace and build core dump structure 49 | std::string stack_trace = Crashser::getStackTrace(); 50 | Crashser::CoreDump core_dump(std::move(stack_trace), &os_info, &app_info); 51 | 52 | // Sending core dump to Crashser collector 53 | Crashser::DumpServerConnector connector("localhost", "8080"); 54 | connector.sendCoreDump(core_dump, [](const std::string& response_body) { 55 | std::cout << response_body << std::endl; // onResponse - isn't an asynchronous operation 56 | }); 57 | } catch (const std::runtime_error& e) { 58 | std::cerr << "Error crash handling: " << e.what() << std::endl; 59 | } 60 | 61 | exit(EXIT_FAILURE); 62 | } 63 | 64 | int main(int argc, char** argv) 65 | { 66 | ::signal(SIGSEGV, &my_signal_handler); 67 | ::raise(SIGSEGV); 68 | return EXIT_SUCCESS; 69 | } 70 | ``` 71 | 72 | Sometimes it is necessary to control the throwing of some exceptions. 73 | With Crashser library you can do it like this: 74 | 75 | ```cpp 76 | #include "crashser.h" 77 | 78 | void functionWithUnhandledException() 79 | { 80 | // You can also throw exception by default and get stack trace, 81 | // but the code below provides information about the exception in more detail 82 | Crashser::throwWithStackTrace(std::logic_error("Unhandled exception")); 83 | } 84 | 85 | int main(int argc, char** argv) 86 | { 87 | try { 88 | functionWithUnhandledException(); 89 | } catch (const std::exception& e) { 90 | try { 91 | // OSInfo name is determined automatically, but you can override it like below 92 | const Crashser::OSInfo os_info("OSX", "11.2.2"); 93 | const Crashser::AppInfo app_info("test_example_app", "0.0.1"); 94 | 95 | // Getting stack trace from exception and build core dump structure 96 | std::string stack_trace = Crashser::getStackTrace(e); 97 | Crashser::CoreDump core_dump(std::move(stack_trace), &os_info, &app_info); 98 | 99 | // Sending core dump to Crashser collector 100 | Crashser::DumpServerConnector connector("localhost", "8080"); 101 | connector.sendCoreDump(core_dump, [](const std::string& response_body) { 102 | std::cout << response_body << std::endl; // onResponse - isn't an asynchronous operation 103 | }); 104 | } catch (const std::exception& e) { 105 | std::cerr << "Error crash handling: " << e.what() << std::endl; 106 | return EXIT_FAILURE; 107 | } 108 | } 109 | 110 | return EXIT_SUCCESS; 111 | } 112 | ``` 113 | 114 | ## License 115 | Information about license will be added later... 116 | -------------------------------------------------------------------------------- /examples/exception_handling_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "crashser.h" 4 | 5 | void functionWithUnhandledException() 6 | { 7 | // You can also throw exception by default and get stack trace, 8 | // but the code below provides information about the exception in more detail 9 | Crashser::throwWithStackTrace(std::logic_error("Unhandled exception")); 10 | } 11 | 12 | int main(int argc, char** argv) 13 | { 14 | try { 15 | functionWithUnhandledException(); 16 | } catch (const std::exception& e) { 17 | try { 18 | // OSInfo name is determined automatically, but you can override it like below 19 | const Crashser::OSInfo os_info("OSX", "11.2.2"); 20 | const Crashser::AppInfo app_info("test_example_app", "0.0.1"); 21 | 22 | // Getting stack trace from exception and build core dump structure 23 | std::string stack_trace = Crashser::getStackTrace(e); 24 | Crashser::CoreDump core_dump(std::move(stack_trace), &os_info, &app_info); 25 | 26 | // Sending core dump to Crashser collector 27 | Crashser::DumpServerConnector connector("localhost", "8080"); 28 | connector.sendCoreDump(core_dump, [](const std::string& response_body) { 29 | std::cout << response_body << std::endl; // onResponse - isn't an asynchronous operation 30 | }); 31 | } catch (const std::exception& e) { 32 | std::cerr << "Error crash handling: " << e.what() << std::endl; 33 | return EXIT_FAILURE; 34 | } 35 | } 36 | 37 | return EXIT_SUCCESS; 38 | } -------------------------------------------------------------------------------- /examples/sigsegv_handling_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "crashser.h" 5 | 6 | void my_signal_handler(int sig_number) 7 | { 8 | try { 9 | // OSInfo name is determined automatically, but you can override it like below 10 | const Crashser::OSInfo os_info("OSX", "11.2.2"); 11 | const Crashser::AppInfo app_info("test_example_app", "0.0.1"); 12 | 13 | // Getting stack trace and build core dump structure 14 | std::string stack_trace = Crashser::getStackTrace(); 15 | Crashser::CoreDump core_dump(std::move(stack_trace), &os_info, &app_info); 16 | 17 | // Sending core dump to Crashser collector 18 | Crashser::DumpServerConnector connector("localhost", "8080"); 19 | connector.sendCoreDump(core_dump, [](const std::string& response_body) { 20 | std::cout << response_body << std::endl; // onResponse - isn't an asynchronous operation 21 | }); 22 | } catch (const std::runtime_error& e) { 23 | std::cerr << "Error crash handling: " << e.what() << std::endl; 24 | } 25 | 26 | exit(EXIT_FAILURE); 27 | } 28 | 29 | int main(int argc, char** argv) 30 | { 31 | ::signal(SIGSEGV, &my_signal_handler); 32 | ::raise(SIGSEGV); 33 | return EXIT_SUCCESS; 34 | } 35 | -------------------------------------------------------------------------------- /include/app_info.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Crashser 6 | { 7 | 8 | class AppInfo 9 | { 10 | public: 11 | AppInfo() = default; 12 | AppInfo(std::string name, std::string version = "", std::string programming_language = "C++") 13 | : m_name{std::move(name)}, m_version{std::move(version)}, m_programming_language{std::move(programming_language)} {} 14 | 15 | void setName(std::string name) { m_name = std::move(name); } 16 | void setVersion(std::string version) { m_version = std::move(version); } 17 | void setProgrammingLanguage(std::string programming_language) { m_programming_language = std::move(programming_language); } 18 | 19 | const std::string& getName() const noexcept { return m_name; }; 20 | const std::string& getVersion() const noexcept { return m_version; }; 21 | const std::string& getProgrammingLanguage() const noexcept { return m_programming_language; }; 22 | private: 23 | std::string m_name; 24 | std::string m_version; 25 | std::string m_programming_language = "C++"; 26 | }; 27 | 28 | } -------------------------------------------------------------------------------- /include/core_dump.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "os_info.hpp" 8 | #include "app_info.hpp" 9 | 10 | namespace Crashser 11 | { 12 | 13 | inline std::uint64_t getCurrentTimestamp() 14 | { 15 | using namespace std::chrono; 16 | return duration_cast(system_clock::now().time_since_epoch()).count(); 17 | } 18 | 19 | class CoreDump 20 | { 21 | public: 22 | CoreDump(std::string core_dump_data, const OSInfo* os_info, const AppInfo* app_info, std::uint64_t timestamp = getCurrentTimestamp()) 23 | : m_os_info{os_info}, m_app_info{app_info}, m_core_dump_data{std::move(core_dump_data)}, m_timestamp{timestamp} {} 24 | void addExtension(std::pair ext) { m_extensions.push_back(std::move(ext)); } 25 | const OSInfo* getOSInfo() const noexcept { return m_os_info; } 26 | const AppInfo* getAppInfo() const noexcept { return m_app_info; } 27 | const std::vector>& getExtensions() const noexcept { return m_extensions; } 28 | const std::string& getCoreDumpData() const noexcept { return m_core_dump_data; } 29 | std::uint64_t getTimestamp() const noexcept { return m_timestamp; } 30 | private: 31 | const OSInfo* m_os_info; 32 | const AppInfo* m_app_info; 33 | std::vector> m_extensions; 34 | std::string m_core_dump_data; 35 | std::uint64_t m_timestamp; 36 | }; 37 | 38 | } -------------------------------------------------------------------------------- /include/crashser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "os_info.hpp" 4 | #include "core_dump.hpp" 5 | #include "app_info.hpp" 6 | #include "stack_trace.hpp" 7 | #include "dump_server_connector.hpp" 8 | -------------------------------------------------------------------------------- /include/dump_server_connector.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace Crashser 11 | { 12 | 13 | class CoreDump; 14 | 15 | class DumpServerConnector : private boost::noncopyable 16 | { 17 | using OnResponse = std::function; 18 | public: 19 | DumpServerConnector(std::string host, std::string port); 20 | void sendCoreDump(const CoreDump& core_dump, OnResponse onResponse = nullptr); 21 | ~DumpServerConnector(); 22 | private: 23 | boost::asio::io_context m_ioc; 24 | boost::asio::ip::tcp::resolver m_resolver; 25 | boost::beast::tcp_stream m_stream; 26 | }; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /include/json_serializer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Crashser 8 | { 9 | 10 | class OSInfo; 11 | class AppInfo; 12 | class CoreDump; 13 | 14 | class JSONSerializer 15 | { 16 | public: 17 | static std::string serialize(const CoreDump& core_dump); 18 | private: 19 | static boost::property_tree::ptree serialize(const OSInfo* os_info); 20 | static boost::property_tree::ptree serialize(const AppInfo* app_info); 21 | }; 22 | 23 | } -------------------------------------------------------------------------------- /include/os_info.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Crashser 7 | { 8 | 9 | class OSInfo 10 | { 11 | public: 12 | OSInfo(std::string name = BOOST_PLATFORM, std::string version = "", std::string architecture = "") 13 | : m_name{std::move(name)}, m_version{std::move(version)}, m_architecture{std::move(architecture)} {} 14 | 15 | void setName(std::string name) { m_name = std::move(name); } 16 | void setArchitecture(std::string architecture) { m_architecture = std::move(architecture); } 17 | void setVersion(std::string version) { m_version = std::move(version); } 18 | 19 | const std::string& getName() const noexcept { return m_name; } 20 | const std::string& getArchitecture() const noexcept { return m_architecture; } 21 | const std::string& getVersion() const noexcept { return m_version; } 22 | private: 23 | std::string m_name; 24 | std::string m_version; 25 | std::string m_architecture; 26 | }; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /include/stack_trace.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef _GNU_SOURCE 4 | #define _GNU_SOURCE 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace Crashser 12 | { 13 | 14 | template 15 | void throwWithStackTrace(const Exception& e) { 16 | using traced = boost::error_info; 17 | auto temp = boost::enable_error_info(e) << traced(boost::stacktrace::stacktrace()); 18 | throw temp; 19 | } 20 | 21 | template 22 | std::string getStackTrace(const Exception& e) 23 | { 24 | using traced = boost::error_info; 25 | const boost::stacktrace::stacktrace* stack_trace = boost::get_error_info(e); 26 | if (stack_trace) { 27 | return to_string(*stack_trace); 28 | } else { 29 | return boost::diagnostic_information(e); 30 | } 31 | } 32 | 33 | inline std::string getStackTrace() 34 | { 35 | return to_string(boost::stacktrace::stacktrace()); 36 | } 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /sources/dump_server_connector.cpp: -------------------------------------------------------------------------------- 1 | #include "dump_server_connector.hpp" 2 | 3 | #include "core_dump.hpp" 4 | #include "json_serializer.hpp" 5 | 6 | Crashser::DumpServerConnector::DumpServerConnector(std::string host, std::string port) 7 | : m_ioc{}, m_resolver{m_ioc}, m_stream{m_ioc} 8 | { 9 | auto const resolve_result = m_resolver.resolve(host, port); 10 | m_stream.connect(resolve_result); 11 | } 12 | 13 | void Crashser::DumpServerConnector::sendCoreDump(const CoreDump& core_dump, OnResponse onResponse) 14 | { 15 | constexpr int HTTP_VERSION = 11; 16 | boost::beast::http::request request{boost::beast::http::verb::post, "/core_dump", HTTP_VERSION}; 17 | request.set(boost::beast::http::field::content_type, "application/json"); 18 | request.set(boost::beast::http::field::user_agent, BOOST_BEAST_VERSION_STRING); 19 | request.body() = JSONSerializer::serialize(core_dump); 20 | boost::beast::http::write(m_stream, request); 21 | 22 | if (onResponse) { 23 | boost::beast::flat_buffer buffer{}; 24 | boost::beast::http::response response{}; 25 | boost::beast::http::read(m_stream, buffer, response); 26 | onResponse(boost::beast::buffers_to_string(response.body().data())); 27 | } 28 | } 29 | 30 | Crashser::DumpServerConnector::~DumpServerConnector() 31 | { 32 | boost::beast::error_code error_code; 33 | m_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, error_code); 34 | } 35 | -------------------------------------------------------------------------------- /sources/json_serializer.cpp: -------------------------------------------------------------------------------- 1 | #include "json_serializer.hpp" 2 | 3 | #include "core_dump.hpp" 4 | 5 | #define PUT_IN_JSON_IF_EXISTS(json, key, value) \ 6 | do { if(!value.empty()) json.put(key, value); } while(false) 7 | 8 | std::string Crashser::JSONSerializer::serialize(const CoreDump& core_dump) 9 | { 10 | boost::property_tree::ptree core_dump_json{}; 11 | boost::property_tree::ptree os_info_json = serialize(core_dump.getOSInfo()); 12 | boost::property_tree::ptree app_info_json = serialize(core_dump.getAppInfo()); 13 | core_dump_json.add_child("os_info", std::move(os_info_json)); 14 | core_dump_json.add_child("app_info", std::move(app_info_json)); 15 | core_dump_json.put("data", core_dump.getCoreDumpData()); 16 | core_dump_json.put("timestamp", core_dump.getTimestamp()); 17 | for (const auto& extension : core_dump.getExtensions()) { 18 | core_dump_json.put(extension.first, extension.second); 19 | } 20 | 21 | std::ostringstream oss{}; 22 | boost::property_tree::json_parser::write_json(oss, core_dump_json); 23 | return oss.str(); 24 | } 25 | 26 | boost::property_tree::ptree Crashser::JSONSerializer::serialize(const OSInfo* os_info) 27 | { 28 | boost::property_tree::ptree os_info_json{}; 29 | if (os_info) { 30 | PUT_IN_JSON_IF_EXISTS(os_info_json, "name", os_info->getName()); 31 | PUT_IN_JSON_IF_EXISTS(os_info_json, "architecture", os_info->getArchitecture()); 32 | PUT_IN_JSON_IF_EXISTS(os_info_json, "version", os_info->getVersion()); 33 | } 34 | return os_info_json; 35 | } 36 | 37 | boost::property_tree::ptree Crashser::JSONSerializer::serialize(const AppInfo* app_info) 38 | { 39 | boost::property_tree::ptree app_info_json{}; 40 | if (app_info) { 41 | PUT_IN_JSON_IF_EXISTS(app_info_json, "name", app_info->getName()); 42 | PUT_IN_JSON_IF_EXISTS(app_info_json, "version", app_info->getVersion()); 43 | PUT_IN_JSON_IF_EXISTS(app_info_json, "programming_language", app_info->getProgrammingLanguage()); 44 | } 45 | return app_info_json; 46 | } 47 | --------------------------------------------------------------------------------