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
2 changes: 1 addition & 1 deletion src/agent/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ if(CMAKE_HOST_WIN32 AND NOT DEFINED VCPKG_TARGET_TRIPLET)
set(VCPKG_TARGET_TRIPLET "x64-windows-static" CACHE STRING "vcpkg triplet for the self-contained agent build")
endif()

project(HsmAgent VERSION 0.5.25 LANGUAGES CXX)
project(HsmAgent VERSION 0.5.26 LANGUAGES CXX)

if(MSVC)
# Static CRT (/MT) to match the static triplet — the exe must run on a clean machine with no VC++
Expand Down
7 changes: 6 additions & 1 deletion src/agent/include/agent/file_logger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ namespace hsm::agent
class FileLogger
{
public:
explicit FileLogger(std::wstring path, std::size_t max_bytes = 5u * 1024u * 1024u);
/// Messages below `min_level` are dropped (default Info — Debug stays out of the file unless
/// explicitly lowered; parity with the collector's built-in file logger).
explicit FileLogger(std::wstring path,
hsm::collector::LogLevel min_level = hsm::collector::LogLevel::Info,
std::size_t max_bytes = 5u * 1024u * 1024u);

FileLogger(const FileLogger&) = delete;
FileLogger& operator=(const FileLogger&) = delete;
Expand All @@ -30,6 +34,7 @@ namespace hsm::agent
std::mutex mutex_;
std::wstring path_;
std::size_t max_bytes_;
hsm::collector::LogLevel min_level_;
std::ofstream out_;
std::size_t written_ = 0;
};
Expand Down
6 changes: 4 additions & 2 deletions src/agent/src/logging/file_logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ namespace hsm::agent
}
} // namespace

FileLogger::FileLogger(std::wstring path, std::size_t max_bytes)
: path_(std::move(path)), max_bytes_(max_bytes)
FileLogger::FileLogger(std::wstring path, hsm::collector::LogLevel min_level, std::size_t max_bytes)
: path_(std::move(path)), max_bytes_(max_bytes), min_level_(min_level)
{
out_.open(path_, std::ios::app | std::ios::binary);
if (out_)
Expand All @@ -65,6 +65,8 @@ namespace hsm::agent

void FileLogger::Write(hsm::collector::LogLevel level, const std::string& message)
{
if (level < min_level_)
return;
std::lock_guard<std::mutex> lock(mutex_);
if (!out_)
return;
Expand Down
3 changes: 3 additions & 0 deletions src/native/collector/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,9 @@ if(HSM_COLLECTOR_HTTP)
add_test(NAME native_http_registers_sensors_on_start
COMMAND hsm_collector_tests native_http_registers_sensors_on_start
)
add_test(NAME native_http_registration_failure_logs_status
COMMAND hsm_collector_tests native_http_registration_failure_logs_status
)
endif()

add_test(NAME native_invalid_argument_clears_out_params
Expand Down
41 changes: 28 additions & 13 deletions src/native/collector/src/hsm_collector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2341,7 +2341,10 @@ namespace
// "Type":0 Command discriminator the server's CommandRequestBaseDeserializationConverter
// keys on. Best-effort: a failure is logged and Start proceeds (values would fail too if the
// server is unreachable; the value queue's durable retry handles a transient outage).
void PostRegistrationsWire(const std::vector<std::shared_ptr<NativeSensor>>& sensors)
// `runtime` = a sensor registered lazily AFTER Start (top-CPU process, network interface,
// service-status, TCP-rate) rather than the connect-time batch. It changes the log label
// (no "on connect") and level (Debug, so per-sample registrations do not spam the log).
void PostRegistrationsWire(const std::vector<std::shared_ptr<NativeSensor>>& sensors, bool runtime = false)
{
if (sensors.empty())
return;
Expand All @@ -2362,12 +2365,22 @@ namespace
};

const auto response = http_transport_->Post(endpoints_.CommandsList(), body, headers);
// Name a single sensor by path; the connect batch by count.
const std::string what = sensors.size() == 1 ? "sensor " + sensors.front()->Path()
: std::to_string(sensors.size()) + " sensor(s)";
if (!response.IsSuccess())
LogError("Failed to register " + std::to_string(sensors.size()) +
" sensor(s) on Start: " + (response.error.empty() ? "non-2xx response" : response.error));
{
// HTTP status for parity with the value send-fail log; fall back to the transport
// error text (e.g. "Could not connect to server") when no HTTP response arrived.
const std::string reason = response.status_code > 0
? "HTTP " + std::to_string(response.status_code)
: (response.error.empty() ? "no response" : response.error);
LogError("Failed to register " + what + (runtime ? "" : " on connect") + ": " + reason);
}
else if (runtime)
LogMessage(HSM_LOG_LEVEL_DEBUG, "Registered " + what + ".");
else
LogMessage(
HSM_LOG_LEVEL_INFO, "Registered " + std::to_string(sensors.size()) + " sensor(s) on connect.");
LogMessage(HSM_LOG_LEVEL_INFO, "Registered " + what + " on connect.");
}
#endif

Expand Down Expand Up @@ -3843,7 +3856,7 @@ namespace
return;
#if defined(HSM_COLLECTOR_HTTP)
if (send_wire_)
PostRegistrationsWire({ tcp_fail_rate_sensor_ });
PostRegistrationsWire({ tcp_fail_rate_sensor_ }, /*runtime=*/true);
#endif

{
Expand Down Expand Up @@ -4014,7 +4027,7 @@ namespace
it = cache.emplace(iface, sensor).first;
#if defined(HSM_COLLECTOR_HTTP)
if (send_wire_)
PostRegistrationsWire({ sensor });
PostRegistrationsWire({ sensor }, /*runtime=*/true);
#endif
}

Expand Down Expand Up @@ -4105,7 +4118,7 @@ namespace
return;
#if defined(HSM_COLLECTOR_HTTP)
if (send_wire_)
PostRegistrationsWire({ service_status_sensor_ });
PostRegistrationsWire({ service_status_sensor_ }, /*runtime=*/true);
#endif

{
Expand Down Expand Up @@ -4353,7 +4366,7 @@ namespace
// so the server gets the description. HttpTransport::Post is
// thread-safe (per-call libcurl easy handle).
if (send_wire_)
PostRegistrationsWire({ std::move(sensor) });
PostRegistrationsWire({ std::move(sensor) }, /*runtime=*/true);
#endif
}
else
Expand Down Expand Up @@ -4500,11 +4513,13 @@ namespace
dropped = DispatchQueuedLocked(lock, /*clear_remainder_on_failure=*/true);
}

// Error routing — shutdown discard: a bounded stop that drops pending values on a
// dead/failing transport reports it (logged outside the queue lock so a slow log
// sink cannot stall shutdown). Deduplicated like any other error.
// Debug, not Error: dropping buffered values on a bounded graceful stop is expected and
// contracted (a stop must not block on a dead/failing transport). Logging it as Error
// spammed the log and the Windows Event Log on every routine restart; keep it only as a
// Debug breadcrumb (logged outside the queue lock so a slow sink cannot stall shutdown).
if (dropped > 0)
LogError("Collector stop dropped " + std::to_string(dropped) + " pending value(s): transport unavailable.");
LogMessage(HSM_LOG_LEVEL_DEBUG,
"Collector stop dropped " + std::to_string(dropped) + " pending value(s): transport unavailable.");
}

// Pops and sends batches of up to max_values_in_package_ until the queue is empty or a
Expand Down
46 changes: 46 additions & 0 deletions src/native/collector/tests/hsm_collector_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3786,6 +3786,51 @@ namespace

Require(hsm_collector_stop(collector.value) == HSM_RESULT_OK, "collector stop failed");
}

// A registration POST that gets a non-2xx response must log the HTTP status for diagnosis (parity
// with the value send-fail log), and the Start batch keeps the "on connect" label — never the
// stale "on Start".
void NativeHttpRegistrationFailureLogsStatus()
{
hsm::test::HttpCaptureServer server(503);

std::vector<std::pair<hsm_log_level_t, std::string>> logs;

hsm_collector_options_t options{};
options.access_key = "reg-key";
options.server_address = "http://127.0.0.1";
options.port = server.Port();
options.client_name = "reg-client";
options.allow_plaintext_transport = true;
options.package_collect_period_ms = 20;

CollectorHandle collector = CreateCollector(options);
hsm_collector_set_logger(
collector.value,
[](hsm_log_level_t level, const char* message, void* user_data) {
static_cast<std::vector<std::pair<hsm_log_level_t, std::string>>*>(user_data)->emplace_back(level, message);
},
&logs);
hsm_collector_test_install_http_sender(collector.value);
SensorHandle sensor = CreateIntSensor(collector.value, "reg/int");

// Registration is POSTed synchronously during Start, so the failure is logged by the time
// start() returns.
Require(hsm_collector_start(collector.value) == HSM_RESULT_OK, "collector start failed");
Require(hsm_collector_stop(collector.value) == HSM_RESULT_OK, "collector stop failed");

const auto error_has = [&](const std::string& needle) {
for (const auto& entry : logs)
if (entry.first == HSM_LOG_LEVEL_ERROR && entry.second.find(needle) != std::string::npos)
return true;
return false;
};
Require(error_has("Failed to register"), "a failed registration must log an error");
Require(error_has("HTTP 503"), "the registration failure must carry the HTTP status code");
Require(error_has("on connect"), "the Start batch failure keeps the 'on connect' label");
for (const auto& entry : logs)
Require(entry.second.find("on Start") == std::string::npos, "the stale 'on Start' label must be gone");
}
#endif

void NativeWireTimeSpanAndVersionMatchNet()
Expand Down Expand Up @@ -5032,6 +5077,7 @@ namespace
{ "native_http_transport_posts_to_capture_server", [](const std::string&) { NativeHttpTransportPostsToCaptureServer(); } },
{ "native_http_live_send_posts_to_capture_server", [](const std::string&) { NativeHttpLiveSendPostsToCaptureServer(); } },
{ "native_http_registers_sensors_on_start", [](const std::string&) { NativeHttpRegistersSensorsOnStart(); } },
{ "native_http_registration_failure_logs_status", [](const std::string&) { NativeHttpRegistrationFailureLogsStatus(); } },
#endif
{ "native_http_endpoint_routing_matches_net", [](const std::string&) { NativeHttpEndpointRoutingMatchesNet(); } },
{ "native_http_retry_policy_matches_net", [](const std::string&) { NativeHttpRetryPolicyMatchesNet(); } },
Expand Down
Loading