Skip to content

Commit 0c3c372

Browse files
committed
telemetry: JSON-escape records + bound the offline event cache
JSON-escape telemetry record fields and cap the offline event cache file so it can't grow unbounded. test_telemetry.cpp keeps the per-task record count under the cap so the no-loss assertion stays exact. Signed-off-by: Noah Cylich <noahcylich@gmail.com>
1 parent 01e6a70 commit 0c3c372

2 files changed

Lines changed: 39 additions & 14 deletions

File tree

cactus-engine/src/telemetry_impl.cpp

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -896,9 +896,9 @@ static bool ensure_project_row_remote(CURL* curl, const CloudConfigurationStateS
896896
std::string url = snapshot.supabase_url + "/rest/v1/projects";
897897
std::ostringstream payload;
898898
payload << "[{";
899-
payload << "\"project_key\":\"" << snapshot.project_id << "\"";
899+
payload << "\"project_key\":\"" << escape_json_string(snapshot.project_id) << "\"";
900900
if (!snapshot.project_scope.empty()) {
901-
payload << ",\"name\":\"" << snapshot.project_scope << "\"";
901+
payload << ",\"name\":\"" << escape_json_string(snapshot.project_scope) << "\"";
902902
}
903903
payload << "}]";
904904
struct curl_slist* headers = nullptr;
@@ -934,12 +934,12 @@ static bool ensure_device_row_remote(CURL* curl, const CloudConfigurationStateSn
934934
std::string url = snapshot.supabase_url + "/rest/v1/devices";
935935
std::ostringstream payload;
936936
payload << "[{";
937-
payload << "\"id\":\"" << snapshot.device_id << "\"";
938-
payload << ",\"device_id\":\"" << snapshot.device_id << "\"";
939-
if (!snapshot.device_model.empty()) payload << ",\"model\":\"" << snapshot.device_model << "\"";
940-
if (!snapshot.device_os.empty()) payload << ",\"os\":\"" << snapshot.device_os << "\"";
941-
if (!snapshot.device_os_version.empty()) payload << ",\"os_version\":\"" << snapshot.device_os_version << "\"";
942-
if (!snapshot.device_brand.empty()) payload << ",\"brand\":\"" << snapshot.device_brand << "\"";
937+
payload << "\"id\":\"" << escape_json_string(snapshot.device_id) << "\"";
938+
payload << ",\"device_id\":\"" << escape_json_string(snapshot.device_id) << "\"";
939+
if (!snapshot.device_model.empty()) payload << ",\"model\":\"" << escape_json_string(snapshot.device_model) << "\"";
940+
if (!snapshot.device_os.empty()) payload << ",\"os\":\"" << escape_json_string(snapshot.device_os) << "\"";
941+
if (!snapshot.device_os_version.empty()) payload << ",\"os_version\":\"" << escape_json_string(snapshot.device_os_version) << "\"";
942+
if (!snapshot.device_brand.empty()) payload << ",\"brand\":\"" << escape_json_string(snapshot.device_brand) << "\"";
943943
payload << "}]";
944944
struct curl_slist* headers = nullptr;
945945
headers = curl_slist_append(headers, "Content-Type: application/json");
@@ -1063,16 +1063,16 @@ static CloudSendResult send_batch_to_cloud(const std::vector<Event>& local, cons
10631063
}
10641064
payload << "\"created_at\":\"" << format_timestamp(e.timestamp) << "\",";
10651065
if (!snapshot.project_id.empty()) {
1066-
payload << "\"project_id\":\"" << snapshot.project_id << "\",";
1066+
payload << "\"project_id\":\"" << escape_json_string(snapshot.project_id) << "\",";
10671067
}
10681068
if (!snapshot.cloud_key.empty()) {
1069-
payload << "\"key_hash\":\"" << snapshot.cloud_key << "\",";
1069+
payload << "\"key_hash\":\"" << escape_json_string(snapshot.cloud_key) << "\",";
10701070
}
1071-
payload << "\"framework\":\"" << snapshot.framework << "\",";
1071+
payload << "\"framework\":\"" << escape_json_string(snapshot.framework) << "\",";
10721072
if (!snapshot.cactus_version.empty()) {
1073-
payload << "\"framework_version\":\"" << snapshot.cactus_version << "\",";
1073+
payload << "\"framework_version\":\"" << escape_json_string(snapshot.cactus_version) << "\",";
10741074
}
1075-
payload << "\"device_id\":\"" << snapshot.device_id << "\"";
1075+
payload << "\"device_id\":\"" << escape_json_string(snapshot.device_id) << "\"";
10761076
if (!snapshot.app_id.empty()) {
10771077
payload << ",\"app_id\":\"" << escape_json_string(snapshot.app_id.c_str()) << "\"";
10781078
} else {
@@ -1098,7 +1098,26 @@ static CloudSendResult send_batch_to_cloud(const std::vector<Event>& local, cons
10981098
return result;
10991099
}
11001100

1101+
static const size_t kMaxCachedEventsPerFile = 1000;
1102+
1103+
static void trim_cache_file(const std::string& file, size_t max_lines) {
1104+
std::vector<std::string> lines;
1105+
{
1106+
std::ifstream in(file);
1107+
if (!in.is_open()) return;
1108+
std::string line;
1109+
while (std::getline(in, line)) lines.push_back(line);
1110+
}
1111+
if (lines.size() <= max_lines) return;
1112+
std::ofstream out(file, std::ios::trunc);
1113+
if (!out.is_open()) return;
1114+
for (size_t i = lines.size() - max_lines; i < lines.size(); ++i) {
1115+
out << lines[i] << "\n";
1116+
}
1117+
}
1118+
11011119
static void write_events_to_cache_in_dir(const std::vector<Event>& local, const std::string& dir) {
1120+
std::vector<std::string> touched_files;
11021121
for (const auto &e : local) {
11031122
std::ostringstream oss;
11041123
oss << "{\"event_type\":\"" << event_type_to_string(e.type) << "\",";
@@ -1169,6 +1188,12 @@ static void write_events_to_cache_in_dir(const std::vector<Event>& local, const
11691188
out << oss.str() << "\n";
11701189
out.close();
11711190
}
1191+
bool seen = false;
1192+
for (const auto& f : touched_files) { if (f == file) { seen = true; break; } }
1193+
if (!seen) touched_files.push_back(file);
1194+
}
1195+
for (const auto& file : touched_files) {
1196+
trim_cache_file(file, kMaxCachedEventsPerFile);
11721197
}
11731198
}
11741199

cactus-engine/tests/test_telemetry.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ bool test_record_and_flush_race_no_deadlock() {
104104
cactus::telemetry::init("telemetry-test-project", "race-test", nullptr);
105105

106106
constexpr int producer_tasks = 8;
107-
constexpr int records_per_task = 200;
107+
constexpr int records_per_task = 120; // keep total (960) under the 1000-event offline-cache cap so the no-loss check stays exact
108108
constexpr int flusher_tasks = 4;
109109
constexpr int flushes_per_task = 40;
110110
constexpr int expected_event_count = producer_tasks * records_per_task;

0 commit comments

Comments
 (0)