From 234c2beef75fb0378e6852250be0ec419dc3321a Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 15 Aug 2018 09:56:59 +0200 Subject: [PATCH] :sparkles: add event sampling #22 (#28) --- .travis.yml | 52 +++-- CMakeLists.txt | 33 ++-- README.md | 11 +- appveyor.yml | 10 +- doc/Doxyfile.in | 4 +- examples/integration/CMakeLists.txt | 19 ++ examples/integration/example.cpp | 11 ++ include/crow/crow.hpp | 85 ++++---- include/crow/integrations/log4cplus.hpp | 39 +++- include/crow/integrations/loggers.hpp | 37 ++++ .../thirdparty/curl_wrapper/curl_wrapper.hpp | 34 +++- maintainer/Makefile | 2 +- src/crow.cpp | 156 +++++++-------- src/crow_config.hpp.in | 35 +++- src/crow_utilities.cpp | 25 +-- src/crow_utilities.hpp | 20 +- tests/livetest.cpp | 2 +- tests/server/server.js | 20 ++ tests/thirdparty/curl_wrapper/README.md | 1 - .../thirdparty/curl_wrapper/curl_wrapper.hpp | 28 --- tests/uncaught_exception.cpp | 29 +++ tests/unittests.cpp | 186 +++++++++++------- 22 files changed, 541 insertions(+), 298 deletions(-) create mode 100644 examples/integration/CMakeLists.txt create mode 100644 examples/integration/example.cpp create mode 100644 tests/server/server.js delete mode 100644 tests/thirdparty/curl_wrapper/README.md delete mode 100644 tests/thirdparty/curl_wrapper/curl_wrapper.hpp create mode 100644 tests/uncaught_exception.cpp diff --git a/.travis.yml b/.travis.yml index 22d4ddd..d7c11e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-4.9', 'ninja-build'] + packages: ['g++-4.9', 'ninja-build', 'nodejs'] before_script: - pip install --user cpp-coveralls after_success: @@ -35,8 +35,8 @@ matrix: # OSX / Clang - - os: osx - osx_image: xcode6.4 + #- os: osx + # osx_image: xcode6.4 - os: osx osx_image: xcode7.3 @@ -78,7 +78,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-4.9', 'ninja-build'] + packages: ['g++-4.9', 'ninja-build', 'nodejs'] - os: linux compiler: gcc @@ -88,7 +88,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-5', 'ninja-build'] + packages: ['g++-5', 'ninja-build', 'nodejs'] - os: linux compiler: gcc @@ -98,7 +98,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-6', 'ninja-build'] + packages: ['g++-6', 'ninja-build', 'nodejs'] - os: linux compiler: gcc @@ -108,7 +108,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-7', 'ninja-build'] + packages: ['g++-7', 'ninja-build', 'nodejs'] - os: linux compiler: gcc @@ -118,7 +118,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-8', 'ninja-build'] + packages: ['g++-8', 'ninja-build', 'nodejs'] - os: linux compiler: gcc @@ -129,7 +129,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-8', 'ninja-build'] + packages: ['g++-8', 'ninja-build', 'nodejs'] # Linux / Clang @@ -141,7 +141,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5'] - packages: ['g++-6', 'clang-3.5', 'ninja-build'] + packages: ['g++-6', 'clang-3.5', 'ninja-build', 'nodejs'] - os: linux compiler: clang @@ -151,7 +151,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.6'] - packages: ['g++-6', 'clang-3.6', 'ninja-build'] + packages: ['g++-6', 'clang-3.6', 'ninja-build', 'nodejs'] - os: linux compiler: clang @@ -161,7 +161,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.7'] - packages: ['g++-6', 'clang-3.7', 'ninja-build'] + packages: ['g++-6', 'clang-3.7', 'ninja-build', 'nodejs'] - os: linux compiler: clang @@ -171,7 +171,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-6', 'clang-3.8', 'ninja-build'] + packages: ['g++-6', 'clang-3.8', 'ninja-build', 'nodejs'] - os: linux compiler: clang @@ -181,7 +181,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-6', 'clang-3.9', 'ninja-build'] + packages: ['g++-6', 'clang-3.9', 'ninja-build', 'nodejs'] - os: linux compiler: clang @@ -191,7 +191,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-4.0'] - packages: ['g++-6', 'clang-4.0', 'ninja-build'] + packages: ['g++-6', 'clang-4.0', 'ninja-build', 'nodejs'] - os: linux compiler: clang @@ -201,7 +201,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0'] - packages: ['g++-6', 'clang-5.0', 'ninja-build'] + packages: ['g++-6', 'clang-5.0', 'ninja-build', 'nodejs'] - os: linux compiler: clang @@ -211,7 +211,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0'] - packages: ['g++-6', 'clang-6.0', 'ninja-build'] + packages: ['g++-6', 'clang-6.0', 'ninja-build', 'nodejs'] - os: linux compiler: clang @@ -222,19 +222,19 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0'] - packages: ['g++-6', 'clang-6.0', 'ninja-build'] + packages: ['g++-6', 'clang-6.0', 'ninja-build', 'nodejs'] ################ # build script # ################ -script: +install: # get CMake and Ninja (only for systems with brew - macOS) - | if [[ (-x $(which brew)) ]]; then brew update - brew install cmake ninja - brew upgrade cmake + brew install cmake ninja node + brew upgrade cmake node cmake --version else wget -O cmake.sh https://cmake.org/files/v3.10/cmake-3.10.0-rc1-Linux-x86_64.sh @@ -243,6 +243,11 @@ script: export PATH=/tmp/cmake/bin:$PATH fi + # install node forever module + - npm install -g forever + # start test server + - forever start tests/server/server.js + # make sure CXX is correctly set - if [[ "${COMPILER}" != "" ]]; then export CXX=${COMPILER}; export CC=${CCOMPILER}; fi @@ -251,8 +256,13 @@ script: - $CC --version - $CXX --version +script: # compile and execute unit tests - mkdir -p build && cd build - cmake .. ${CMAKE_OPTIONS} -DCROW_BUILD_LOG4CPLUS=ON -GNinja && cmake --build . --config Release - ctest -C Release -V -j - cd .. + +after_script: + # shut down test server + - forever stop tests/server/server.js diff --git a/CMakeLists.txt b/CMakeLists.txt index 643c1cd..b68a744 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.10) cmake_policy(SET CMP0048 NEW) -project(crow VERSION 0.0.4) +project(crow VERSION 0.0.5) ############################## # check for required headers # @@ -103,7 +103,7 @@ find_package(Threads) # library # ########### -add_library(crow src/crow.cpp src/crow_utilities.cpp) +add_library(crow src/crow.cpp src/crow_utilities.cpp src/crow_utilities.hpp) set_target_properties(crow PROPERTIES CXX_STANDARD 11) target_include_directories(crow PUBLIC include PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CURL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIRS}) target_link_libraries(crow ${CURL_LIBRARIES} ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES}) @@ -136,19 +136,25 @@ option(CROW_BUILD_LOG4CPLUS "build log4cplus example" OFF) if(CROW_BUILD_TESTING AND (BUILD_TESTING OR CROW_EXTERNAL_CURL_PROJECT)) enable_testing() - get_target_property(CROW_SOURCES crow SOURCES) - add_executable(tests tests/unittests.cpp ${CROW_SOURCES}) - set_target_properties(tests PROPERTIES CXX_STANDARD 11) - target_include_directories(tests PUBLIC tests include ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) - target_link_libraries(tests ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) + add_executable(unittests tests/unittests.cpp) + set_target_properties(unittests PROPERTIES CXX_STANDARD 11) + target_include_directories(unittests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} tests) + target_link_libraries(unittests crow) + add_test(NAME unittests COMMAND unittests) + + # std::current_exception is broken with MSVC (https://developercommunity.visualstudio.com/content/problem/135332/stdcurrent-exception-returns-null-in-a-stdterminat.html) + if (NOT MSVC) + add_executable(uncaught_exception tests/uncaught_exception.cpp) + set_target_properties(uncaught_exception PROPERTIES CXX_STANDARD 11) + target_link_libraries(uncaught_exception crow) + add_test(NAME uncaught_exception COMMAND uncaught_exception) + endif() add_executable(livetest tests/livetest.cpp) set_target_properties(livetest PROPERTIES CXX_STANDARD 11) - target_include_directories(livetest PUBLIC include ${CMAKE_CURRENT_BINARY_DIR}) + target_include_directories(livetest PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(livetest crow) - - add_test(tests tests) - add_test(livetest livetest) + add_test(NAME livetest COMMAND livetest) if(CROW_BUILD_LOG4CPLUS) include(ExternalProject) @@ -163,12 +169,11 @@ if(CROW_BUILD_TESTING AND (BUILD_TESTING OR CROW_EXTERNAL_CURL_PROJECT)) set_target_properties(log4cplus PROPERTIES IMPORTED_LOCATION ${install_dir}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}log4cplus${CMAKE_STATIC_LIBRARY_SUFFIX}) add_dependencies(log4cplus log4cplus_project) - add_executable(log4cplus_example examples/log4cplus/example.cpp) + add_executable(log4cplus_example examples/log4cplus/example.cpp include/crow/integrations/loggers.hpp include/crow/integrations/log4cplus.hpp) set_target_properties(log4cplus_example PROPERTIES CXX_STANDARD 11) target_include_directories(log4cplus_example PUBLIC include ${install_dir}/include) target_link_libraries(log4cplus_example log4cplus crow) - - add_test(log4cplus_example log4cplus_example) + add_test(NAME log4cplus_example COMMAND log4cplus_example) endif() endif() diff --git a/README.md b/README.md index 0056d66..e112730 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ ### Setup -- `nlohmann::crow::crow(dsn, context={}, install_handlers=true)` to create a client +- `nlohmann::crow::crow(dsn, context={}, sample_rate=1.0, install_handlers=true)` to create a client - `nlohmann::crow::install_handler()` to later install termination handler ### Reporting @@ -54,13 +54,20 @@ The following items from [the SDK implementation guidelines](https://docs.sentry - [x] Non-blocking event submission - [ ] Basic data sanitization (e.g. filtering out values that look like passwords) - [x] Context data helpers (e.g. setting the current user, recording breadcrumbs) - - [ ] Event sampling + - [x] Event sampling - [ ] Honor Sentry’s HTTP 429 Retry-After header - [ ] Pre and Post event send hooks - [ ] Local variable values in stacktrace (on platforms where this is possible) ## Change Log +### Version 0.0.5 + +- :sparkles: added event sampling +- :white_check_mark: added testing server to mock Sentry +- :memo: added example code +- :hammer: overworked threading + ### Version 0.0.4 - :sparkles: added Log4cplus integration diff --git a/appveyor.yml b/appveyor.yml index d662b25..a09acc6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -48,6 +48,8 @@ install: - if "%COMPILER%"=="mingw" set PATH=C:\projects\deps\ninja;%PATH% - if "%COMPILER%"=="mingw" set PATH=C:\mingw-w64\x86_64-7.3.0-posix-seh-rt_v5-rev0\mingw64\bin;%PATH% - if "%COMPILER%"=="mingw" g++ --version + - ps: Install-Product node 6 + - npm install -g forever before_build: - curl https://curl.haxx.se/download/curl-7.61.0.zip -O @@ -59,5 +61,11 @@ before_build: build_script: - cmake --build . --config Release +before_test: + - forever start tests/server/server.js + test_script: - - ctest -C Release -V -j --output-on-failure --verbose + - ctest -C Release -V --output-on-failure --verbose + +on_finish: + - forever stop tests/server/server.js diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 087d0bb..a0a8925 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -95,7 +95,7 @@ ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = NO SHOW_FILES = NO -SHOW_NAMESPACES = NO +SHOW_NAMESPACES = YES FILE_VERSION_FILTER = LAYOUT_FILE = CITE_BIB_FILES = @@ -115,7 +115,7 @@ WARN_LOGFILE = #--------------------------------------------------------------------------- INPUT = @CMAKE_CURRENT_SOURCE_DIR@/include/crow @CMAKE_CURRENT_SOURCE_DIR@/src \ @CMAKE_CURRENT_SOURCE_DIR@/examples \ - @CMAKE_CURRENT_BINARY_DIR@/include/crow @CMAKE_CURRENT_SOURCE_DIR@/README.md + @CMAKE_CURRENT_BINARY_DIR@/src @CMAKE_CURRENT_SOURCE_DIR@/README.md INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.c \ *.cc \ diff --git a/examples/integration/CMakeLists.txt b/examples/integration/CMakeLists.txt new file mode 100644 index 0000000..db20f67 --- /dev/null +++ b/examples/integration/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.10) + +project(crow_example) + +include(ExternalProject) +ExternalProject_Add(crow_project + GIT_REPOSITORY https://github.com/nlohmann/crow + INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/crow + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/crow -DCROW_BUILD_TESTING=OFF + BUILD_BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/log4cplus/lib/${CMAKE_SHARED_LIBRARY_PREFIX}crow${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ExternalProject_Get_Property(crow_project install_dir) +add_library(crow SHARED IMPORTED) +set_target_properties(crow PROPERTIES IMPORTED_LOCATION ${install_dir}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}crow${CMAKE_SHARED_LIBRARY_SUFFIX}) +add_dependencies(crow crow_project) + +add_executable(example example.cpp) +target_link_libraries(example crow) +set_target_properties(example PROPERTIES CXX_STANDARD 11) diff --git a/examples/integration/example.cpp b/examples/integration/example.cpp new file mode 100644 index 0000000..1215857 --- /dev/null +++ b/examples/integration/example.cpp @@ -0,0 +1,11 @@ +#include + +using nlohmann::crow; + +int main() +{ + crow client("http://abc:cde@sentry.io/42"); + + client.add_breadcrumb("this is a breadcrumb"); + client.capture_message("this is a message"); +} diff --git a/include/crow/crow.hpp b/include/crow/crow.hpp index 60cabe4..8fb1df0 100644 --- a/include/crow/crow.hpp +++ b/include/crow/crow.hpp @@ -1,7 +1,7 @@ /* _____ _____ _____ _ _ _ | | __ | | | | | Crow - a Sentry client for C++ -| --| -| | | | | | version 0.0.4 +| --| -| | | | | | version 0.0.5 |_____|__|__|_____|_____| https://github.com/nlohmann/crow Licensed under the MIT License . @@ -27,20 +27,27 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*! + * @file crow.hpp + * @brief main header for Crow + */ + #ifndef NLOHMANN_CROW_HPP #define NLOHMANN_CROW_HPP -#include -#include +#include // vector +#include // future +#include // mutex +#include //string #include using json = nlohmann::json; -/// namespace for Niels Lohmann +/*! + * @brief namespace for Niels Lohmann + */ namespace nlohmann { -class crow; - /*! * @brief a C++ client for Sentry */ @@ -52,7 +59,8 @@ class crow * * @param[in] dsn the DNS string * @param[in] context an optional attributes object - * @param[in] install_handlers whether to install a termination handler + * @param[in] sample_rate the sample rate (0.0 .. 1.0, default: 1.0) + * @param[in] install_handlers whether to install a termination handler (default: off) * * @throw std::invalid_argument if DNS string is invalid * @throw std::invalid_argument if context object contains invalid key @@ -72,7 +80,8 @@ class crow */ explicit crow(const std::string& dsn, const json& context = nullptr, - bool install_handlers = true); + double sample_rate = 1.0, + bool install_handlers = false); /*! * @brief install termination handler to handle uncaught exceptions @@ -82,26 +91,6 @@ class crow */ void install_handler(); - /*! - * @brief copy constructor - * - * @param[in] other client to copy - * - * @note The last event id is not preserved by copying. - * - * @since 0.0.2 - */ - crow(const crow& other); - - /*! - * @brief destructor - * - * @note Waits until pending HTTP requests complete. - * - * @since 0.0.2 - */ - ~crow(); - /*! * @name event capturing * @{ @@ -112,22 +101,18 @@ class crow * * @param[in] message the message to capture * @param[in] attributes an optional attributes object - * @param[in] asynchronous whether the message should be sent asynchronously * * @throw std::invalid_argument if context object contains invalid key * * @since 0.0.1 */ void capture_message(const std::string& message, - const json& attributes = nullptr, - bool asynchronous = true); - + const json& attributes = nullptr); /*! * @brief capture an exception * * @param[in] exception the passed exception * @param[in] context an optional context object - * @param[in] asynchronous whether the message should be sent asynchronously * @param[in] handled whether the exception was handled and only reported * * @throw std::invalid_argument if context object contains invalid key @@ -136,7 +121,6 @@ class crow */ void capture_exception(const std::exception& exception, const json& context = nullptr, - bool asynchronous = true, bool handled = true); /*! @@ -242,21 +226,28 @@ class crow /*! * @brief POST the payload to the Sentry sink URL * - * @param[in] payload payload to send (copy intended) + * @param[in] payload payload to send + * @param[in] synchronous whether the payload should be sent immediately * @return result */ std::string post(json payload) const; - void enqueue_post(bool asynchronous); + void enqueue_post(); /*! * @brief termination handler that detects uncaught exceptions * * @post previously installed termination handler is executed + * + * @note The rethrowing of uncaught exceptions does not work with Microsoft Visual Studio 2017, see + * https://developercommunity.visualstudio.com/content/problem/135332/stdcurrent-exception-returns-null-in-a-stdterminat.html */ static void new_termination_handler(); private: + /// the sample rate (as integer 0..100) + const int m_sample_rate; + /// whether the client is enabled const bool m_enabled = true; /// the public key to be used in requests @@ -265,18 +256,28 @@ class crow std::string m_secret_key; /// the URL to send events to std::string m_store_url; + /// the payload of all events json m_payload = {}; - /// the result of the last HTTP POST - mutable std::future m_pending_future; - /// the termination handler installed before initializing the client - std::terminate_handler existing_termination_handler = nullptr; /// a mutex to make payload thread-safe std::mutex m_payload_mutex; - /// a mutex to make the posting thread-safe - mutable std::mutex m_pending_future_mutex; + + /// a vector of POST jobs + mutable std::vector> m_jobs; + /// a mutex to make m_jobs thread-safe + mutable std::mutex m_jobs_mutex; + /// a cache for the last event id + mutable std::string m_last_event_id = "-1"; + /// whether a post has been made already + bool m_posts = false; + + /// the termination handler installed before initializing the client + std::terminate_handler existing_termination_handler = nullptr; /// a pointer to the last client (used for termination handling) static crow* m_client_that_installed_termination_handler; + + /// the maximal number of running jobs + static constexpr std::size_t m_maximal_jobs = 10; }; } diff --git a/include/crow/integrations/log4cplus.hpp b/include/crow/integrations/log4cplus.hpp index 320f1a0..7cfbac2 100644 --- a/include/crow/integrations/log4cplus.hpp +++ b/include/crow/integrations/log4cplus.hpp @@ -1,3 +1,32 @@ +/* + _____ _____ _____ _ _ _ +| | __ | | | | | Crow - a Sentry client for C++ +| --| -| | | | | | version 0.0.5 +|_____|__|__|_____|_____| https://github.com/nlohmann/crow + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2018 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #ifndef NLOHMANN_CROW_LOG4CPLUS_HPP #define NLOHMANN_CROW_LOG4CPLUS_HPP @@ -8,14 +37,22 @@ #include #include +/*! + * @file log4cplus.hpp + * @brief interface for the Log4cplus appender + */ + namespace nlohmann { namespace crow_integrations { /*! - * @brief appender for Log4cplus * @example log4cplus/example.cpp + */ + +/*! + * @brief appender for Log4cplus * @since 0.0.4 */ class log4cplus_appender : public log4cplus::Appender diff --git a/include/crow/integrations/loggers.hpp b/include/crow/integrations/loggers.hpp index 08114b2..bb03f96 100644 --- a/include/crow/integrations/loggers.hpp +++ b/include/crow/integrations/loggers.hpp @@ -1,8 +1,45 @@ +/* + _____ _____ _____ _ _ _ +| | __ | | | | | Crow - a Sentry client for C++ +| --| -| | | | | | version 0.0.5 +|_____|__|__|_____|_____| https://github.com/nlohmann/crow + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2018 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #ifndef NLOHMANN_CROW_LOGGERS_HPP #define NLOHMANN_CROW_LOGGERS_HPP +/*! + * @file loggers.hpp + * @brief common types for logger integrations + */ + namespace nlohmann { +/*! + * @brief integrations of Crow into other frameworks + */ namespace crow_integrations { diff --git a/include/thirdparty/curl_wrapper/curl_wrapper.hpp b/include/thirdparty/curl_wrapper/curl_wrapper.hpp index 3f825b7..53687d3 100644 --- a/include/thirdparty/curl_wrapper/curl_wrapper.hpp +++ b/include/thirdparty/curl_wrapper/curl_wrapper.hpp @@ -6,10 +6,26 @@ #include #include "thirdparty/json/json.hpp" -using json = nlohmann::json; - class curl_wrapper { + public: + class response + { + public: + response(std::string d, int sc) + : data(std::move(d)) + , status_code(sc) + {} + + std::string data; + int status_code; + + nlohmann::json json() + { + return nlohmann::json::parse(data); + } + }; + public: curl_wrapper() : m_curl(curl_easy_init()) { @@ -23,16 +39,15 @@ class curl_wrapper { curl_slist_free_all(m_headers); curl_easy_cleanup(m_curl); - curl_global_cleanup(); } - std::string post(const std::string& url, const json& payload, const bool compress = false) + response post(const std::string& url, const nlohmann::json& payload, const bool compress = false) { set_header("Content-Type: application/json"); return post(url, payload.dump(), compress); } - std::string post(const std::string& url, const std::string& data, const bool compress = false) + response post(const std::string& url, const std::string& data, const bool compress = false) { std::string c_data; @@ -41,7 +56,7 @@ class curl_wrapper c_data = compress_string(data); set_header("Content-Encoding: gzip"); - std::string size_header = "Content-Length: " + std::to_string(c_data.size()); + const std::string size_header = "Content-Length: " + std::to_string(c_data.size()); set_header(size_header.c_str()); set_option(CURLOPT_POSTFIELDS, c_data.c_str()); set_option(CURLOPT_POSTFIELDSIZE, c_data.size()); @@ -65,7 +80,10 @@ class curl_wrapper throw std::runtime_error(error_msg); } - return string_buffer; + int status_code; + curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &status_code); + + return {std::move(string_buffer), status_code}; } template @@ -116,7 +134,7 @@ class curl_wrapper // For the compress zs.next_in = (Bytef*)str.data(); - zs.avail_in = str.size(); // set the z_stream's input + zs.avail_in = static_cast(str.size()); // set the z_stream's input char outbuffer[32768]; diff --git a/maintainer/Makefile b/maintainer/Makefile index 7f008e9..bfedea2 100644 --- a/maintainer/Makefile +++ b/maintainer/Makefile @@ -18,6 +18,6 @@ upload_docs: coverage: mkdir build ; cd build ; cmake .. -DCROW_CHECK_COVERAGE=ON ; cmake --build . ; cd .. - cd build ; ./tests ; cd .. + cd build ; ctest ; cd .. cd build ; cmake --build . --target crow_coverage_report ; cd .. open build/coverage_report/index.html diff --git a/src/crow.cpp b/src/crow.cpp index 9750bd3..8034167 100644 --- a/src/crow.cpp +++ b/src/crow.cpp @@ -1,7 +1,7 @@ /* _____ _____ _____ _ _ _ | | __ | | | | | Crow - a Sentry client for C++ -| --| -| | | | | | version 0.0.4 +| --| -| | | | | | version 0.0.5 |_____|__|__|_____|_____| https://github.com/nlohmann/crow Licensed under the MIT License . @@ -27,15 +27,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include +/*! + * @file crow.cpp + * @brief implementation of class crow + */ + +#include // current_exception, exception, get_terminate, rethrow_exception, set_terminate +#include // regex, regex_match, smatch +#include // invalid_argument +#include // stringstream #include #include #include @@ -46,12 +46,16 @@ using json = nlohmann::json; namespace nlohmann { +class crow; + crow* crow::m_client_that_installed_termination_handler = nullptr; crow::crow(const std::string& dsn, const json& context, + const double sample_rate, const bool install_handlers) - : m_enabled(not dsn.empty()) + : m_sample_rate(static_cast(sample_rate * 100.0)) + , m_enabled(not dsn.empty()) { // process DSN if (not dsn.empty()) @@ -89,7 +93,7 @@ void crow::install_handler() { if (existing_termination_handler == nullptr) { - existing_termination_handler = std::set_terminate([]() {}); + existing_termination_handler = std::get_terminate(); std::set_terminate(&new_termination_handler); // we remember this client, because we will use it to report @@ -98,26 +102,8 @@ void crow::install_handler() } } -crow::crow(const crow& other) - : m_enabled(other.m_enabled) - , m_public_key(other.m_public_key) - , m_secret_key(other.m_secret_key) - , m_store_url(other.m_store_url) - , m_payload(other.m_payload) -{} - -crow::~crow() -{ - // wait fur running request to finish - if (m_pending_future.valid()) - { - m_pending_future.wait(); - } -} - void crow::capture_message(const std::string& message, - const json& attributes, - const bool asynchronous) + const json& attributes) { std::lock_guard lock(m_payload_mutex); m_payload["message"] = message; @@ -151,13 +137,12 @@ void crow::capture_message(const std::string& message, } } - enqueue_post(asynchronous); + enqueue_post(); } void crow::capture_exception(const std::exception& exception, const json& context, - const bool asynchronous, const bool handled) { std::stringstream thread_id; @@ -175,7 +160,10 @@ void crow::capture_exception(const std::exception& exception, // add given context merge_context(context); - enqueue_post(asynchronous); + enqueue_post(); + + // we want to support at most m_maximal_jobs running jobs + m_jobs.reserve(m_maximal_jobs); } void crow::add_breadcrumb(const std::string& message, @@ -228,22 +216,20 @@ void crow::add_breadcrumb(const std::string& message, std::string crow::get_last_event_id() const { - std::lock_guard lock(m_pending_future_mutex); - if (m_pending_future.valid()) + if (not m_posts) { - try - { - return json::parse(m_pending_future.get()).at("id"); - } - catch (const json::exception&) - { - return ""; - } + return ""; } - else + + std::lock_guard lock(m_jobs_mutex); + if (not m_jobs.empty() and m_jobs.back().valid()) { - return ""; + m_last_event_id = m_jobs.back().get(); + m_jobs.clear(); } + + assert(not m_last_event_id.empty()); + return m_last_event_id; } const json& crow::get_context() const @@ -352,22 +338,56 @@ void crow::clear_context() std::string crow::post(json payload) const { - if (m_enabled) + curl_wrapper curl; + + // add security header + std::string security_header = "X-Sentry-Auth: Sentry sentry_version=5,sentry_client=crow/"; + security_header += std::string(NLOHMANN_CROW_VERSION) + ",sentry_timestamp="; + security_header += std::to_string(crow_utilities::get_timestamp()); + security_header += ",sentry_key=" + m_public_key; + security_header += ",sentry_secret=" + m_secret_key; + curl.set_header(security_header.c_str()); + + return curl.post(m_store_url, payload, true).data; +} + +void crow::enqueue_post() +{ + if (not m_enabled) { - curl_wrapper curl; + return; + } - // add security header - std::string security_header = "X-Sentry-Auth: Sentry sentry_version=5,sentry_client=crow/"; - security_header += std::string(NLOHMANN_CROW_VERSION) + ",sentry_timestamp="; - security_header += std::to_string(crow_utilities::get_timestamp()); - security_header += ",sentry_key=" + m_public_key; - security_header += ",sentry_secret=" + m_secret_key; - curl.set_header(security_header.c_str()); + // https://docs.sentry.io/clientdev/features/#event-sampling + const auto rand = crow_utilities::get_random_number(0, 99); + if (rand >= m_sample_rate) + { + return; + } - return curl.post(m_store_url, payload); + // we want to change the job list + std::lock_guard lock_jobs(m_jobs_mutex); + + // remember we made a post and now can rely on a last id + m_posts = true; + + // enforce maximal number of running jobs + if (m_jobs.size() == m_maximal_jobs) + { + // clearing the vector of futures means waiting for their result; we do + // not need to save an event id, because we will add another post job + // below + m_jobs.clear(); } - return ""; + // add the new job + m_jobs.emplace_back(std::async(std::launch::async, [this]() + { + return json::parse(post(m_payload)).at("id").get(); + })); + + assert(not m_jobs.empty()); + assert(m_jobs.back().valid()); } void crow::new_termination_handler() @@ -384,29 +404,11 @@ void crow::new_termination_handler() } catch (const std::exception& e) { - m_client_that_installed_termination_handler->capture_exception(e, nullptr, false, false); + m_client_that_installed_termination_handler->capture_exception(e, nullptr, false); } } m_client_that_installed_termination_handler->existing_termination_handler(); } - void crow::enqueue_post(const bool asynchronous) - { - std::lock_guard lock(m_pending_future_mutex); - - // wait for running post requests - if (m_pending_future.valid()) - { - m_pending_future.wait(); - } - - // start a new post request - m_pending_future = std::async(std::launch::async, [this] { return post(m_payload); }); - if (not asynchronous) - { - m_pending_future.wait(); - } - } - -} \ No newline at end of file +} diff --git a/src/crow_config.hpp.in b/src/crow_config.hpp.in index cfdc81b..04343ac 100644 --- a/src/crow_config.hpp.in +++ b/src/crow_config.hpp.in @@ -1,7 +1,7 @@ /* _____ _____ _____ _ _ _ | | __ | | | | | Crow - a Sentry client for C++ -| --| -| | | | | | version 0.0.4 +| --| -| | | | | | version 0.0.5 |_____|__|__|_____|_____| https://github.com/nlohmann/crow Licensed under the MIT License . @@ -30,28 +30,43 @@ SOFTWARE. #ifndef NLOHMANN_CROW_CONFIG_HPP #define NLOHMANN_CROW_CONFIG_HPP +/*! + * @file crow_config.hpp + * @brief constants written by CMake + */ + +// macros do decide which headers are present #cmakedefine NLOHMANN_CROW_HAVE_CXXABI_H #cmakedefine NLOHMANN_CROW_HAVE_EXECINFO_H #cmakedefine NLOHMANN_CROW_HAVE_DLFCN_H +// macros to choose certain functions #cmakedefine NLOHMANN_CROW_MINGW -#define NLOHMANN_CROW_CMAKE_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}" -#define NLOHMANN_CROW_CMAKE_SYSTEM_VERSION "${CMAKE_SYSTEM_VERSION}" -#define NLOHMANN_CROW_CMAKE_SYSTEM_PROCESSOR "${CMAKE_SYSTEM_PROCESSOR}" -#define NLOHMANN_CROW_CMAKE_CXX_COMPILER_ID "${CMAKE_CXX_COMPILER_ID}" -#define NLOHMANN_CROW_CMAKE_CXX_COMPILER_VERSION "${CMAKE_CXX_COMPILER_VERSION}" +// context: app #define NLOHMANN_CROW_CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" +#define NLOHMANN_CROW_BITS (${CMAKE_SIZEOF_VOID_P} * 8) + +// context: device +#define NLOHMANN_CROW_CMAKE_SYSTEM_PROCESSOR "${CMAKE_SYSTEM_PROCESSOR}" #define NLOHMANN_CROW_HOSTNAME "${NLOHMANN_CROW_HOSTNAME}" +#define NLOHMANN_CROW_SYSCTL_HW_MODEL "${NLOHMANN_CROW_SYSCTL_HW_MODEL}" #define NLOHMANN_CROW_TOTAL_PHYSICAL_MEMORY (${NLOHMANN_CROW_TOTAL_PHYSICAL_MEMORY} * 1048576ul) + +// context: os +#define NLOHMANN_CROW_CMAKE_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}" +#define NLOHMANN_CROW_OS_RELEASE "${NLOHMANN_CROW_OS_RELEASE}" +#define NLOHMANN_CROW_OS_VERSION "${NLOHMANN_CROW_OS_VERSION}" +#define NLOHMANN_CROW_CMAKE_SYSTEM_VERSION "${CMAKE_SYSTEM_VERSION}" #define NLOHMANN_CROW_UNAME "${NLOHMANN_CROW_UNAME}" #define NLOHMANN_CROW_SYSTEMINFO "${NLOHMANN_CROW_SYSTEMINFO}" + +// context: runtime +#define NLOHMANN_CROW_CMAKE_CXX_COMPILER_ID "${CMAKE_CXX_COMPILER_ID}" +#define NLOHMANN_CROW_CMAKE_CXX_COMPILER_VERSION "${CMAKE_CXX_COMPILER_VERSION}" #define NLOHMANN_CROW_CXX "${NLOHMANN_CROW_CXX}" -#define NLOHMANN_CROW_SYSCTL_HW_MODEL "${NLOHMANN_CROW_SYSCTL_HW_MODEL}" -#define NLOHMANN_CROW_OS_RELEASE "${NLOHMANN_CROW_OS_RELEASE}" -#define NLOHMANN_CROW_OS_VERSION "${NLOHMANN_CROW_OS_VERSION}" -#define NLOHMANN_CROW_BITS (${CMAKE_SIZEOF_VOID_P} * 8) +// crow version #define NLOHMANN_CROW_VERSION_MAJOR ${PROJECT_VERSION_MAJOR} #define NLOHMANN_CROW_VERSION_MINOR ${PROJECT_VERSION_MINOR} #define NLOHMANN_CROW_VERSION_PATCH ${PROJECT_VERSION_PATCH} diff --git a/src/crow_utilities.cpp b/src/crow_utilities.cpp index 89dd73b..4e92fa1 100644 --- a/src/crow_utilities.cpp +++ b/src/crow_utilities.cpp @@ -1,7 +1,7 @@ /* _____ _____ _____ _ _ _ | | __ | | | | | Crow - a Sentry client for C++ -| --| -| | | | | | version 0.0.4 +| --| -| | | | | | version 0.0.5 |_____|__|__|_____|_____| https://github.com/nlohmann/crow Licensed under the MIT License . @@ -27,7 +27,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*! + * @file crow_utilities.cpp + * @brief implementation of Crow helper functions + */ + #include +#include +#include +#include +#include #include #include #include @@ -150,14 +159,6 @@ std::string get_iso8601() return buf; } -/*! - * @brief return a random integer - * @param[in] lower lower bound - * @param[in] upper upper bound - * @return lower <= x <= upper - * - * @note The C++11 random implementation is broken in MinGW, so we need to fall back to std::rand(). - */ int get_random_number(int lower, int upper) { #ifdef NLOHMANN_CROW_MINGW @@ -169,9 +170,9 @@ int get_random_number(int lower, int upper) } return std::rand() % upper + lower; #else - static std::random_device random_device; - static std::default_random_engine random_engine(random_device()); - static std::uniform_int_distribution uniform_dist(lower, upper); + std::random_device random_device; + std::default_random_engine random_engine(random_device()); + std::uniform_int_distribution uniform_dist(lower, upper); return uniform_dist(random_engine); #endif } diff --git a/src/crow_utilities.hpp b/src/crow_utilities.hpp index ef95a4b..baf5200 100644 --- a/src/crow_utilities.hpp +++ b/src/crow_utilities.hpp @@ -1,7 +1,7 @@ /* _____ _____ _____ _ _ _ | | __ | | | | | Crow - a Sentry client for C++ -| --| -| | | | | | version 0.0.4 +| --| -| | | | | | version 0.0.5 |_____|__|__|_____|_____| https://github.com/nlohmann/crow Licensed under the MIT License . @@ -30,6 +30,11 @@ SOFTWARE. #ifndef NLOHMANN_CROW_UTILITIES_HPP #define NLOHMANN_CROW_UTILITIES_HPP +/*! + * @file crow_utilities.hpp + * @brief helper functions for Crow + */ + #include #include #include @@ -38,6 +43,9 @@ using json = nlohmann::json; namespace nlohmann { +/*! + * @brief helper functions for Crow + */ namespace crow_utilities { @@ -52,6 +60,16 @@ json get_backtrace(int skip = 1); std::string pretty_name(const char* type_id_name, bool only_module = false); +/*! + * @brief return a random integer + * @param[in] lower lower bound + * @param[in] upper upper bound + * @return lower <= x <= upper + * + * @note The C++11 random implementation is broken in MinGW, so we need to fall back to std::rand(). + */ +int get_random_number(int lower, int upper); + /*! * @brief get the seconds since epoch * @return seconds since epoch diff --git a/tests/livetest.cpp b/tests/livetest.cpp index 0bb494c..6d50493 100644 --- a/tests/livetest.cpp +++ b/tests/livetest.cpp @@ -6,7 +6,7 @@ using crow = nlohmann::crow; int main() { - auto c = crow("https://fad7ed01056940969a519aba36dc0b2f:3787b21e465845a09d781ab9eb048ae7@sentry.io/1253079"); + crow c("https://fad7ed01056940969a519aba36dc0b2f:3787b21e465845a09d781ab9eb048ae7@sentry.io/1253079"); std::cout << "created client" << std::endl; c.add_breadcrumb(__DATE__ " " __TIME__); diff --git a/tests/server/server.js b/tests/server/server.js new file mode 100644 index 0000000..c56cdbd --- /dev/null +++ b/tests/server/server.js @@ -0,0 +1,20 @@ +const http = require('http'); +const zlib = require('zlib'); + +http.createServer((request, response) => { + let body = []; + request.on('error', (err) => { + console.error(err); + }).on('data', (chunk) => { + body.push(chunk); + }).on('end', () => { + if (request.headers['content-encoding'] == 'gzip') { + body = zlib.gunzipSync(Buffer.concat(body)).toString(); + } + else { + body = Buffer.concat(body).toString(); + } + response.setHeader('Content-Type', 'application/json'); + response.end('{"id":' + JSON.stringify(body) + '}') + }); +}).listen(5000, '127.0.0.1'); diff --git a/tests/thirdparty/curl_wrapper/README.md b/tests/thirdparty/curl_wrapper/README.md deleted file mode 100644 index 934257d..0000000 --- a/tests/thirdparty/curl_wrapper/README.md +++ /dev/null @@ -1 +0,0 @@ -This is just a stub for the curl_wrapper.hpp \ No newline at end of file diff --git a/tests/thirdparty/curl_wrapper/curl_wrapper.hpp b/tests/thirdparty/curl_wrapper/curl_wrapper.hpp deleted file mode 100644 index f3b03e2..0000000 --- a/tests/thirdparty/curl_wrapper/curl_wrapper.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include - -using json = nlohmann::json; -extern json results; - -/// a counter how often POST has been called -size_t message_count = 0; - -class curl_wrapper -{ - public: - std::string post(const std::string& url, const json& payload, bool = false) - { - results.push_back({{"payload", payload}, {"url", url}}); - return "{\"id\":\"" + std::to_string(message_count++) + "\"}"; - } - - std::string post(const std::string& url, const std::string& data, bool = false) - { - results.push_back({{"payload", data}, {"url", url}}); - return "{\"id\":\"" + std::to_string(message_count++) + "\"}"; - } - - void set_header(const char*) - {} -}; diff --git a/tests/uncaught_exception.cpp b/tests/uncaught_exception.cpp new file mode 100644 index 0000000..0bcd0ce --- /dev/null +++ b/tests/uncaught_exception.cpp @@ -0,0 +1,29 @@ +#include +#include +#include + +using crow = nlohmann::crow; + +crow* client = nullptr; + +// a termination handler that checks the state of the messages sent to Sentry +void my_termination_handler() +{ + std::string last_id = client->get_last_event_id(); + const bool success = not last_id.empty(); + delete client; + std::exit(success ? 0 : 1); +} + +int main() +{ + // register our checking termination handler + std::set_terminate(my_termination_handler); + + // define a client and install the handlers + client = new crow("http://abc:def@127.0.0.1:5000/1"); + client->install_handler(); + + // throw an uncaught exception + throw std::runtime_error("oops!"); +} diff --git a/tests/unittests.cpp b/tests/unittests.cpp index 49015e5..162d165 100644 --- a/tests/unittests.cpp +++ b/tests/unittests.cpp @@ -3,24 +3,25 @@ #include #include #include -#include -#include using json = nlohmann::json; using crow = nlohmann::crow; -json results = json::array(); -void verify_message_structure(const json& msg); -void verify_message_structure(const json& msg) +json parse_msg(const std::string& raw); +json parse_msg(const std::string& raw) { + CAPTURE(raw); + auto msg = json::parse(raw); + CAPTURE(msg); CHECK(msg.at("event_id").is_string()); CHECK(msg.at("timestamp").is_string()); - //CHECK(msg.at("logger").is_string()); CHECK(msg.at("platform").is_string()); CHECK(msg.at("sdk").at("name").is_string()); CHECK(msg.at("sdk").at("version").is_string()); + + return msg; } TEST_CASE("utilities") @@ -89,26 +90,41 @@ TEST_CASE("DSN parsing") } } +TEST_CASE("sample rate") +{ + SECTION("sample rate 0.0") + { + crow crow_client("http://abc:def@127.0.0.1:5000/1", nullptr, 0.0); + crow_client.capture_message("message"); + + // make sure no message was sent + CHECK(crow_client.get_last_event_id().empty()); + } + + SECTION("sample rate 1.0") + { + crow crow_client("http://abc:def@127.0.0.1:5000/1", nullptr, 1.0); + crow_client.capture_message("message"); + + // make sure a message was sent + CHECK(not crow_client.get_last_event_id().empty()); + } +} + TEST_CASE("creating messages") { - crow crow_client("https://abc:def@sentry.io/123"); - std::string url = "https://sentry.io/api/123/store/"; + crow crow_client("http://abc:def@127.0.0.1:5000/1"); SECTION("capture_message") { SECTION("without payload") { - results.clear(); std::string msg_string = "message text"; - crow_client.capture_message(msg_string, nullptr, false); - - CHECK(results.size() == 1); - const auto& message = results.at(0).at("payload"); - verify_message_structure(message); - CHECK(message.at("message") == msg_string); - CHECK(results.at(0).at("url") == url); + crow_client.capture_message(msg_string); - CHECK(crow_client.get_last_event_id() == "0"); + // check payload sent to Sentry + auto msg = parse_msg(crow_client.get_last_event_id()); + CHECK(msg["message"] == msg_string); } } @@ -116,51 +132,29 @@ TEST_CASE("creating messages") { SECTION("marked as handled") { - results.clear(); std::string ex_string = "exception text"; - crow_client.capture_exception(std::runtime_error(ex_string), nullptr, false, true); - - CHECK(results.size() == 1); - const auto& message = results.at(0).at("payload"); - CAPTURE(message); - verify_message_structure(message); - CHECK(message.at("exception").size() == 1); - const auto& exception = message.at("exception").at(0); -#ifdef NLOHMANN_CROW_HAVE_CXXABI_H - CHECK(exception.at("type") == "std::runtime_error"); -#endif - CHECK(exception.at("value") == ex_string); - CHECK(exception.at("mechanism").at("handled")); - CHECK(results.at(0).at("url") == url); - - CHECK(crow_client.get_last_event_id() == "1"); + crow_client.capture_exception(std::runtime_error(ex_string), nullptr, true); + + // check payload sent to Sentry + auto msg = parse_msg(crow_client.get_last_event_id()); + CHECK(msg["exception"][0]["value"] == ex_string); + CHECK(msg["exception"][0]["mechanism"]["handled"]); } SECTION("marked as unhandled") { - results.clear(); std::string ex_string = "exception text"; - crow_client.capture_exception(std::runtime_error(ex_string), nullptr, false, false); - - const auto& message = results.at(0).at("payload"); - CAPTURE(message); - verify_message_structure(message); - CHECK(message.at("exception").size() == 1); - const auto& exception = message.at("exception").at(0); -#ifdef NLOHMANN_CROW_HAVE_CXXABI_H - CHECK(exception.at("type") == "std::runtime_error"); -#endif - CHECK(exception.at("value") == ex_string); - CHECK(not exception.at("mechanism").at("handled")); - CHECK(results.at(0).at("url") == url); - - CHECK(crow_client.get_last_event_id() == "2"); + crow_client.capture_exception(std::runtime_error(ex_string), nullptr, false); + + // check payload sent to Sentry + auto msg = parse_msg(crow_client.get_last_event_id()); + CHECK(msg["exception"][0]["value"] == ex_string); + CHECK(not msg["exception"][0]["mechanism"]["handled"]); } } SECTION("add_breadcrumb") { - results.clear(); const std::string msg1 = "breadcrumb 1"; const std::string msg2 = "breadcrumb 2"; const json data2 = {{"from", "origin"}, {"to", "destination"}}; @@ -171,53 +165,93 @@ TEST_CASE("creating messages") // capture message std::string msg_string = "message text"; - crow_client.capture_message(msg_string, nullptr, false); + crow_client.capture_message(msg_string); - CHECK(results.size() == 1); - const auto& message = results.at(0).at("payload"); - verify_message_structure(message); - CHECK(message.at("message") == msg_string); - CHECK(results.at(0).at("url") == url); - - CHECK(message.at("breadcrumbs").at("values").size() == 2); - CHECK(message.at("breadcrumbs").at("values").at(0).at("message") == msg1); - CHECK(message.at("breadcrumbs").at("values").at(0).at("type") == "default"); - - CHECK(message.at("breadcrumbs").at("values").at(1).at("message") == msg2); - CHECK(message.at("breadcrumbs").at("values").at(1).at("type") == "navigation"); - CHECK(message.at("breadcrumbs").at("values").at(1).at("data") == data2); - - CHECK(crow_client.get_last_event_id() == "3"); + // check payload sent to Sentry + auto msg = parse_msg(crow_client.get_last_event_id()); + CHECK(msg["breadcrumbs"]["values"].size() == 2); + CHECK(msg["breadcrumbs"]["values"][0]["message"] == msg1); + CHECK(msg["breadcrumbs"]["values"][1]["message"] == msg2); } } +TEST_CASE("job list") +{ + crow crow_client("http://abc:def@127.0.0.1:5000/1"); + crow_client.capture_message("message_1"); + crow_client.capture_message("message_2"); + crow_client.capture_message("message_3"); + crow_client.capture_message("message_4"); + crow_client.capture_message("message_5"); + crow_client.capture_message("message_6"); + crow_client.capture_message("message_7"); + crow_client.capture_message("message_8"); + crow_client.capture_message("message_9"); + crow_client.capture_message("message_10"); + crow_client.capture_message("message_11"); +} + TEST_CASE("context") { - crow crow_client("https://abc:def@sentry.io/123"); + crow crow_client("http://abc:def@127.0.0.1:5000/1"); SECTION("user context") { - crow_client.add_user_context({{"email", "person@example.com"}}); - CHECK(crow_client.get_context()["user"]["email"] == "person@example.com"); + // set email + const std::string email = "person@example.com"; + crow_client.add_user_context({{"email", email}}); + + // capture message + crow_client.capture_message("msg"); + + // check payload sent to Sentry + auto msg = parse_msg(crow_client.get_last_event_id()); + CHECK(msg["user"]["email"] == email); } SECTION("tags context") { - crow_client.add_tags_context({{"tag", "value"}}); - CHECK(crow_client.get_context()["tags"]["tag"] == "value"); + // set tag + const json tag = {{"tag", "value"}}; + crow_client.add_tags_context(tag); + + // capture message + crow_client.capture_message("msg"); + + // check payload sent to Sentry + auto msg = parse_msg(crow_client.get_last_event_id()); + CHECK(msg["tags"] == tag); } SECTION("request context") { + // set request context crow_client.add_request_context({{"url", "http://example.com"}, {"method", "GET"}}); - CHECK(crow_client.get_context()["request"]["url"] == "http://example.com"); - CHECK(crow_client.get_context()["request"]["method"] == "GET"); + + // capture message + crow_client.capture_message("msg"); + + // check payload sent to Sentry + auto msg = parse_msg(crow_client.get_last_event_id()); + + // check payload sent to Sentry + CHECK(msg["request"]["url"] == "http://example.com"); + CHECK(msg["request"]["method"] == "GET"); } SECTION("extra context") { - crow_client.add_extra_context({{"foo", "bar"}}); - CHECK(crow_client.get_context()["extra"]["foo"] == "bar"); + // set extra context + const json extra = {{"foo", "bar"}}; + crow_client.add_extra_context(extra); + + // capture message + crow_client.capture_message("msg"); + + // check payload sent to Sentry + auto msg = parse_msg(crow_client.get_last_event_id()); + + CHECK(msg["extra"] == extra); } SECTION("reset context")