diff --git a/README.md b/README.md index 68c3d8f..34a8228 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ using namespace Statsd; int main() { // Define the client on localhost, with port 8080, using a prefix and a batching size of 20 bytes - StatsdClient client{ "127.0.0.1", 8080, "myPrefix.", 20 }; + StatsdClient client{ "127.0.0.1", 8080, "myPrefix", 20 }; // Increment the metric "coco" client.increment("coco"); @@ -85,7 +85,7 @@ struct MyApp { m_client.count("bar", 3); } private: - StatsdClient m_client{"localhost", 8125, "foo."}; + StatsdClient m_client{"localhost", 8125, "foo"}; }; int main() { @@ -110,12 +110,12 @@ using namespace Statsd; int main() { // Define the client on localhost, with port 8080, using a prefix and a batching size of 20 bytes - StatsdClient client{ "127.0.0.1", 8080, "myPrefix.", 20 }; + StatsdClient client{ "127.0.0.1", 8080, "myPrefix", 20 }; client.increment("coco"); // Set a new configuration, using a different port and a different prefix - client.setConfig("127.0.0.1", 8000, "anotherPrefix."); + client.setConfig("127.0.0.1", 8000, "anotherPrefix"); client.decrement("kiki"); } diff --git a/include/cpp-statsd-client/StatsdClient.hpp b/include/cpp-statsd-client/StatsdClient.hpp index c05e360..9c2e933 100644 --- a/include/cpp-statsd-client/StatsdClient.hpp +++ b/include/cpp-statsd-client/StatsdClient.hpp @@ -17,6 +17,12 @@ namespace Statsd { * This is the Statsd client, exposing the classic methods * and relying on a UDP sender for the actual sending. * + * The prefix for a stat is provided once at construction or + * on reconfiguring the client. The separator character '.' + * is automatically inserted between the prefix and the stats + * key, therefore you should neither append one to the prefix + * nor prepend one to the key + * * The sampling frequency can be input, as well as the * batching size. The latter is the optional limit in * number of bytes a batch of stats can be before it is @@ -86,11 +92,21 @@ class StatsdClient { mutable std::mt19937 m_randomEngine; }; +namespace detail { +std::string sanitizePrefix(std::string prefix) { + // For convenience we provide the dot when generating the stat message + if (!prefix.empty() && prefix.back() == '.') { + prefix.pop_back(); + } + return prefix; +} +} // namespace detail + inline StatsdClient::StatsdClient(const std::string& host, const uint16_t port, const std::string& prefix, const uint64_t batchsize) noexcept - : m_prefix(prefix), m_sender(new UDPSender{host, port, batchsize}) { + : m_prefix(detail::sanitizePrefix(prefix)), m_sender(new UDPSender{host, port, batchsize}) { // Initialize the random generator to be used for sampling seed(); } @@ -99,7 +115,7 @@ inline void StatsdClient::setConfig(const std::string& host, const uint16_t port, const std::string& prefix, const uint64_t batchsize) noexcept { - m_prefix = prefix; + m_prefix = detail::sanitizePrefix(prefix); m_sender.reset(new UDPSender(host, port, batchsize)); } @@ -150,18 +166,26 @@ inline void StatsdClient::send(const std::string& key, } // Prepare the buffer and include the sampling rate if it's not 1.f + const bool needs_dot = !m_prefix.empty() && !key.empty(); std::string buffer(256, '\0'); int string_len = -1; if (isFrequencyOne) { // Sampling rate is 1.0f, no need to specify it - string_len = std::snprintf( - &buffer.front(), buffer.size(), "%s%s:%d|%s", m_prefix.c_str(), key.c_str(), value, type.c_str()); + string_len = std::snprintf(&buffer.front(), + buffer.size(), + "%s%s%s:%d|%s", + m_prefix.c_str(), + needs_dot ? "." : "", + key.c_str(), + value, + type.c_str()); } else { // Sampling rate is different from 1.0f, hence specify it string_len = std::snprintf(&buffer.front(), buffer.size(), - "%s%s:%d|%s|@%.2f", + "%s%s%s:%d|%s|@%.2f", m_prefix.c_str(), + needs_dot ? "." : "", key.c_str(), value, type.c_str(), diff --git a/tests/testStatsdClient.cpp b/tests/testStatsdClient.cpp index eca74fa..08f3975 100644 --- a/tests/testStatsdClient.cpp +++ b/tests/testStatsdClient.cpp @@ -57,12 +57,23 @@ void testReconfigure() { throw std::runtime_error("Incorrect stat received"); } - client.setConfig("localhost", 8125, "second."); + client.setConfig("localhost", 8125, "second"); client.send("bar", 1, "c", 1.f); if (server.receive() != "second.bar:1|c") { throw std::runtime_error("Incorrect stat received"); } + client.setConfig("localhost", 8125, ""); + client.send("third.baz", 1, "c", 1.f); + if (server.receive() != "third.baz:1|c") { + throw std::runtime_error("Incorrect stat received"); + } + + client.send("", 1, "c", 1.f); + if (server.receive() != ":1|c") { + throw std::runtime_error("Incorrect stat received"); + } + // TODO: test what happens with the batching after resolving the question about incomplete // batches being dropped vs sent on reconfiguring } @@ -90,7 +101,7 @@ void testSendRecv(uint64_t batchSize) { throwOnError(client); expected.emplace_back("sendRecv.kiki:-1|c"); - // Adjusts "toto" by +3 + // Adjusts "toto" by +2 client.seed(19); // this seed gets a hit on the first call client.count("toto", 2, 0.1f); throwOnError(client); @@ -112,9 +123,9 @@ void testSendRecv(uint64_t batchSize) { expected.emplace_back("sendRecv.myTiming:2|ms|@0.10"); // Send a metric explicitly - client.send("tutu", 4, "c", 2.0f); + client.send("tutu", 241, "c", 2.0f); throwOnError(client); - expected.emplace_back("sendRecv.tutu:4|c"); + expected.emplace_back("sendRecv.tutu:241|c"); } // Signal the mock server we are done