Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ jobs:
experimental: true
- name: static-custom-http
flags: -DCOUNTLY_USE_SQLITE=ON -DBUILD_SHARED_LIBS=OFF -DCOUNTLY_USE_CUSTOM_HTTP=ON
- name: static-builtin-http
flags: -DCOUNTLY_USE_SQLITE=ON -DBUILD_SHARED_LIBS=OFF
- name: system-json
flags: -DCOUNTLY_USE_SQLITE=ON -DCOUNTLY_USE_SYSTEM_JSON=ON
apt: nlohmann-json3-dev
- name: custom-sha256
flags: -DCOUNTLY_USE_SQLITE=ON -DCOUNTLY_USE_CUSTOM_SHA256=ON
- name: system-sqlite
Expand Down Expand Up @@ -157,6 +162,62 @@ jobs:
- name: Run tests
run: ./build/countly-tests

# ──────────────────────────────────────────────
# Installed SDK: an app that sees only the install
# prefix must compile, link and run against it.
# ──────────────────────────────────────────────
install:
name: install-${{ matrix.libs }}
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- libs: shared
shared: ON
- libs: static
shared: OFF
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: "recursive"

- name: Install dependencies
run: |
sudo apt-get $APT_OPTS update
sudo apt-get $APT_OPTS install -y --no-install-recommends \
libcurl4-openssl-dev libssl-dev

- name: Build and install
run: |
cmake -DBUILD_SHARED_LIBS=${{ matrix.shared }} -DCMAKE_BUILD_TYPE=Release -B build .
cmake --build build -j 4
cmake --install build --prefix "$RUNNER_TEMP/countly"

- name: Compile and run an app against the installed SDK
working-directory: ${{ runner.temp }}
run: |
cat > app.cpp << 'EOF'
#include "countly.hpp"
#include <iostream>

int main() {
(void)cly::Countly::getInstance();
cly::Event event("install_check", 1);
event.addSegmentation("source", "installed tree");
std::cout << event.serialize() << std::endl;
return 0;
}
EOF
if [ "${{ matrix.libs }}" = "static" ]; then
g++ -std=c++14 -I countly/include app.cpp countly/lib/libcountly.a -lcurl -lssl -lcrypto -lpthread -o app
else
g++ -std=c++14 -I countly/include app.cpp -L countly/lib -lcountly -Wl,-rpath,"$PWD/countly/lib" -o app
fi
./app

# ──────────────────────────────────────────────
# Release tooling smoke test: the SBOM generator must
# not be discovered broken on release day.
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## XX.XX.XX
- ! Minor breaking change ! Builds now stop with a clear error when the SDK headers find a nlohmann/json older than 3.11 first on the include path, instead of mixing two versions of the library.

- Added the `COUNTLY_USE_SYSTEM_JSON` CMake option for building against the application's own nlohmann/json (3.11 or newer) instead of the bundled copy (default `OFF`).
- Added support for static library builds with the built-in HTTP client (libcurl) on Linux and macOS.

- Fixed session, view and timed event durations changing when the system clock was adjusted while the app was running.
- Fixed `cmake --install` producing an include folder that could not be compiled against.

## 26.8.0
- ! Minor breaking change ! The SDK now enforces internal limits on recorded data (key length, value size, segmentation entry count, breadcrumb count, stack trace lines per thread and line length) across events, views, crashes, and user properties. Defaults can be overridden by server-side SDK Behavior Settings, or during init via `setMaxKeyLength`, `setMaxValueSize`, `setMaxSegmentationValues`, `setMaxBreadcrumbCount`, `setMaxStackTraceLinesPerThread` and `setMaxStackTraceLineLength`.
- ! Minor breaking change ! Updated the bundled nlohmann/json from v3.7.0 to v3.12.0. JSON types are part of the public API, so applications must be recompiled against the new headers; binaries built against the old headers will not link against a library built with the new ones.
Expand Down
37 changes: 26 additions & 11 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ option(COUNTLY_USE_CUSTOM_HTTP "Use a custom HTTP library" OFF)
option(COUNTLY_USE_CUSTOM_SHA256 "Use a custom SHA 256 library" OFF)
option(COUNTLY_USE_SQLITE "Use SQLite" OFF)
option(COUNTLY_USE_SYSTEM_SQLITE "Link the system SQLite instead of compiling the vendored amalgamation" OFF)
option(COUNTLY_USE_SYSTEM_JSON "Compile against the system nlohmann_json instead of the vendored copy" OFF)
option(COUNTLY_BUILD_TESTS "Build test programs" OFF)
option(COUNTLY_BUILD_SAMPLE "Build Sample programs" OFF)

Expand All @@ -23,25 +24,28 @@ message("Use a custom HTTP library:" ${COUNTLY_USE_CUSTOM_HTTP})
message("Use a custom SHA 256 library:" ${COUNTLY_USE_CUSTOM_SHA256})
message("Use SQLite:" ${COUNTLY_USE_SQLITE})
message("Use system SQLite:" ${COUNTLY_USE_SYSTEM_SQLITE})
message("Use system nlohmann_json:" ${COUNTLY_USE_SYSTEM_JSON})
message("Build test programs:" ${COUNTLY_BUILD_TESTS})
message("Build Sample programs:" ${COUNTLY_BUILD_SAMPLE})

if (NOT WIN32 AND NOT BUILD_SHARED_LIBS AND NOT COUNTLY_USE_CUSTOM_HTTP)
message(FATAL_ERROR "You must provide a custom HTTP function when compiling statically.")
endif()

# Compile countly public headers
# Public headers installed under include/countly; countly.hpp goes to the include root, see the install rules
set(COUNTLY_PUBLIC_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/include/countly.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/configuration_module.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/configuration_provider.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/constants.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/countly_configuration.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/crash_module.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/event.hpp

${CMAKE_CURRENT_SOURCE_DIR}/include/countly/logger_module.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/crash_module.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/remote_config_store.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/request_builder.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/request_module.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/storage_module_base.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/countly/views_module.hpp)

add_library(countly
${COUNTLY_PUBLIC_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/include/countly.hpp
${CMAKE_CURRENT_SOURCE_DIR}/src/countly.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views_module.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/logger_module.cpp
Expand All @@ -66,7 +70,15 @@ set_target_properties(countly PROPERTIES
find_package(Threads)
target_link_libraries(countly Threads::Threads)

target_include_directories(countly PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/vendor/json/include)
# nlohmann/json is part of the public API, so it is a usage requirement of the countly target
if(COUNTLY_USE_SYSTEM_JSON)
find_package(nlohmann_json 3.11 REQUIRED)
target_link_libraries(countly nlohmann_json::nlohmann_json)
else()
target_include_directories(countly PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/vendor/json/include>
$<INSTALL_INTERFACE:include>)
endif()
if (COUNTLY_BUILD_TESTS)
message("Compiling definitions for tests")
target_compile_definitions(countly PRIVATE COUNTLY_BUILD_TESTS)
Expand Down Expand Up @@ -154,7 +166,6 @@ if(COUNTLY_BUILD_TESTS)
target_compile_definitions(countly-tests PRIVATE COUNTLY_USE_CUSTOM_SHA256)
endif()
target_include_directories(countly-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vendor/doctest/doctest)
target_include_directories(countly-tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/vendor/json/include)
target_link_libraries(countly-tests countly)
set_target_properties(countly-tests PROPERTIES
CXX_STANDARD 14
Expand All @@ -177,7 +188,6 @@ if(COUNTLY_USE_SQLITE)
endif()
endif()
target_include_directories(countly-sample PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vendor/doctest/doctest)
target_include_directories(countly-sample PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/vendor/json/include)
target_link_libraries(countly-sample countly)
set_target_properties(countly-sample PROPERTIES
CXX_STANDARD 14
Expand All @@ -190,3 +200,8 @@ install(TARGETS countly
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
PUBLIC_HEADER DESTINATION include/countly)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/countly.hpp DESTINATION include)
# The installed headers include nlohmann/json.hpp, so the bundled copy ships with them
if(NOT COUNTLY_USE_SYSTEM_JSON)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/vendor/json/include/nlohmann DESTINATION include)
endif()
7 changes: 4 additions & 3 deletions include/countly.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -464,17 +464,18 @@ class Countly : public cly::CountlyDelegates {

void _changeDeviceIdWithoutMerge(const std::string &value);

std::chrono::system_clock::duration getSessionDuration(std::chrono::system_clock::time_point now);
std::chrono::steady_clock::duration getSessionDuration(std::chrono::steady_clock::time_point now);

std::chrono::system_clock::duration getSessionDuration();
std::chrono::steady_clock::duration getSessionDuration();

void updateLoop();
void packEvents();
bool began_session = false;
std::atomic<bool> is_being_disposed{false};
std::atomic<bool> is_sdk_initialized{false};

std::chrono::system_clock::time_point last_sent_session_request;
// steady_clock, so a system clock change can't distort the session duration
std::chrono::steady_clock::time_point last_sent_session_request;
nlohmann::json session_params;

std::unique_ptr<std::thread> thread;
Expand Down
4 changes: 4 additions & 0 deletions include/countly/constants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
#define COUNTLY_CONSTANTS_HPP_

#include "nlohmann/json.hpp"

// From 3.11 on nlohmann/json tags its namespace with its version, so a copy other than the library's fails to link instead of corrupting memory
static_assert(NLOHMANN_JSON_VERSION_MAJOR == 3 && NLOHMANN_JSON_VERSION_MINOR >= 11, "Countly SDK requires nlohmann/json 3.11 or newer (3.x). An incompatible copy of nlohmann/json was found first on the include path. To use your own copy, build the SDK with COUNTLY_USE_SYSTEM_JSON.");

#include <cassert>
#include <chrono>
#include <climits>
Expand Down
2 changes: 2 additions & 0 deletions include/countly/event.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class Event {
nlohmann::json object;
bool timer_running;
std::chrono::system_clock::time_point timestamp;
// steady_clock, so a system clock change can't distort the timed duration
std::chrono::steady_clock::time_point timer_start;
};
} // namespace cly
#endif
12 changes: 6 additions & 6 deletions src/countly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1389,7 +1389,7 @@ bool Countly::beginSession() {

requestModule->addRequestToQueue(data);
session_params.erase("user_details");
last_sent_session_request = Countly::getTimestamp();
last_sent_session_request = std::chrono::steady_clock::now();
began_session = true;
// snapshot guarded state before releasing the lock
bool shouldUpdateRemoteConfig = remote_config_enabled;
Expand Down Expand Up @@ -1568,8 +1568,8 @@ bool Countly::endSession() {
log(LogLevel::ERROR, "[Countly] endSession, Session tracking is disabled in server configuration, can not end session.");
return false;
}
const std::chrono::system_clock::time_point now = Countly::getTimestamp();
const auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
const auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(Countly::getTimestamp().time_since_epoch());
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();

// lock_guard so the mutex is released on scope exit, including the early
// returns below and any exception (e.g. a json type_error from a session_params
Expand Down Expand Up @@ -1933,13 +1933,13 @@ std::string Countly::calculateChecksum(const std::string &salt, const std::strin
#endif
}

std::chrono::system_clock::duration Countly::getSessionDuration(std::chrono::system_clock::time_point now) {
std::chrono::steady_clock::duration Countly::getSessionDuration(std::chrono::steady_clock::time_point now) {
std::lock_guard<std::mutex> lk(*mutex);
std::chrono::system_clock::duration duration = now - last_sent_session_request;
std::chrono::steady_clock::duration duration = now - last_sent_session_request;
return duration;
}

std::chrono::system_clock::duration Countly::getSessionDuration() { return Countly::getSessionDuration(Countly::getTimestamp()); }
std::chrono::steady_clock::duration Countly::getSessionDuration() { return Countly::getSessionDuration(std::chrono::steady_clock::now()); }

void Countly::updateLoop() {
log(LogLevel::DEBUG, "[Countly][updateLoop]");
Expand Down
5 changes: 3 additions & 2 deletions src/event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Event::Event(const std::string &key, size_t count, double sum, double duration)

void Event::setTimestamp() {
timestamp = std::chrono::system_clock::now();
timer_start = std::chrono::steady_clock::now();
object["timestamp"] = std::chrono::duration_cast<std::chrono::milliseconds>(timestamp.time_since_epoch()).count();

std::time_t time = std::chrono::system_clock::to_time_t(timestamp);
Expand All @@ -45,8 +46,8 @@ void Event::startTimer() {

void Event::stopTimer() {
if (timer_running) {
auto now = std::chrono::system_clock::now();
object["dur"] = std::chrono::duration_cast<std::chrono::seconds>(now - timestamp).count();
auto now = std::chrono::steady_clock::now();
object["dur"] = std::chrono::duration_cast<std::chrono::seconds>(now - timer_start).count();
timer_running = false;
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/views_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ class ViewsModule::ViewModuleImpl {
public:
std::string name;
std::string viewId;
std::chrono::seconds startTime;
// steady_clock, so a system clock change can't distort the view duration
std::chrono::steady_clock::time_point startTime;
};

private:
Expand Down Expand Up @@ -67,9 +68,8 @@ class ViewsModule::ViewModuleImpl {
}
} else {

const std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
const auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
std::chrono::seconds dur = timestamp - v->startTime;
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
const auto dur = std::chrono::duration_cast<std::chrono::seconds>(now - v->startTime);
duration = dur.count();
}

Expand Down Expand Up @@ -107,7 +107,7 @@ class ViewsModule::ViewModuleImpl {
ViewModuleImpl::ViewInfo *v = new ViewModuleImpl::ViewInfo();
v->name = limitedName;
v->viewId = cly::utils::generateEventID();
v->startTime = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch());
v->startTime = std::chrono::steady_clock::now();

std::shared_ptr<ViewModuleImpl::ViewInfo> ptr(v);

Expand Down
Loading