From 21cdadb66c906c19f1b21687512dcb081caed7c9 Mon Sep 17 00:00:00 2001 From: Roni Riska Date: Tue, 3 Sep 2019 11:58:07 +0300 Subject: [PATCH] Add tracer configuration The tracer can be configured with environment variables. By default a no-op tracer is still create. Change-Id: I508b69fdc62ff46f3978c6204824c4900a5c6e3c Signed-off-by: Roni Riska --- CMakeLists.txt | 3 +- README.md | 36 ++++++++++++-- src/config.hpp | 46 ++++++++++++++++++ src/tracelib.cpp | 95 +++++++++++++++++++++++++++++++++++-- tst/testcreate.cpp | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 306 insertions(+), 8 deletions(-) create mode 100644 src/config.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a8d101..e44fb55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,7 @@ set(CMAKE_CXX_FLAGS_RELEASE "-O2") set (tracelibcpp_VERSION_MAJOR "0") set (tracelibcpp_VERSION_MINOR "0") -set (tracelibcpp_VERSION_MICRO "3") +set (tracelibcpp_VERSION_MICRO "4") set (tracelibcpp_VERSION_STRING "${tracelibcpp_VERSION_MAJOR}.${tracelibcpp_VERSION_MINOR}.${tracelibcpp_VERSION_MICRO}") @@ -83,6 +83,7 @@ find_package(jaegertracing CONFIG REQUIRED) option(WITH_TESTING "Include using testing support" OFF) include_directories ("${PROJECT_SOURCE_DIR}/include/tracelibcpp") +include_directories ("${PROJECT_SOURCE_DIR}/src") add_library(tracelibcpp SHARED src/tracelib.cpp diff --git a/README.md b/README.md index 2acd2dd..4f44ce3 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # RIC tracing helper library -The library includes a function for creating a tracer instance. - -ToDo: tracer configuration options +The library includes a function for creating a configured tracer instance. +It hides the underlaying tracer implementation from the application. ## Usage @@ -20,6 +19,22 @@ The [opentracing C++](https://github.com/opentracing/opentracing-cpp) Readme giv how span context **inject** and **extract** with textmap can be done. Serialization to JSON can be done with any JSON library. +## Configuration + +The trace library currently supports only [Jaeger](https://www.jaegertracing.io/) [C++ client](https://github.com/jaegertracing/jaeger-client-cpp) tracer implementation. +The configuration is done using environment variables: + +| environment variable | values | default | +| ---------------------------- |------------------------------------ | -------------- | +| TRACING_ENABLED | 1, true, 0, false | false | +| TRACING_JAEGER_SAMPLER_TYPE | const, propabilistic, ratelimiting | const | +| TRACING_JAEGER_SAMPLER_PARAM | float | 0.001 | +| TRACING_JAEGER_AGENT_ADDR | IP addr + port | 127.0.0.1:6831 | +| TRACING_JAEGER_LOG_LEVEL | all, error, none | none | + +Meaning of the configuration variables is described in Jaeger web pages. +By default a no-op tracer is created. + ## Requires cmake @@ -46,6 +61,21 @@ lcov -c --no-external --base-directory $(dirname $PWD) --directory . --output-f genhtml cov.info ``` +## Binary package support +Binary packages of the libary can be created with `make package` target, or with +the Dockerfile in the `ci` directory. + +The Docker build executes unit tests and compiles binary packages which can then be +exported from the container by running it and giving the target directory as a command line +argument. The target directory must mounted to the container. + +```shell +# Build the container +docker build -t tracelibcpp -f ci/Dockerfile . +# Export binary packages to /tmp +docker run -v /tmp:/tmp tracelibcpp /tmp +``` + ## License See [LICENSES.txt](LICENSES.txt) file. diff --git a/src/config.hpp b/src/config.hpp new file mode 100644 index 0000000..5fb4ab8 --- /dev/null +++ b/src/config.hpp @@ -0,0 +1,46 @@ +#ifndef _TRACELIB_CONFIG_HPP_ +#define _TRACELIB_CONFIG_HPP_ + +#include +#include + +#define TRACING_ENABLED_ENV "TRACING_ENABLED" +#define JAEGER_SAMPLER_TYPE_ENV "TRACING_JAEGER_SAMPLER_TYPE" +#define JAEGER_SAMPLER_PARAM_ENV "TRACING_JAEGER_SAMPLER_PARAM" +#define JAEGER_AGENT_ADDR_ENV "TRACING_JAEGER_AGENT_ADDR" +#define JAEGER_LOG_LEVEL_ENV "TRACING_JAEGER_LOG_LEVEL" + +namespace tracelibcpp +{ + typedef enum { + LOG_ALL, + LOG_ERR, + LOG_NONE + } LogLevel; + class ConfMaker { + public: + ConfMaker(std::string serviceName): + name(serviceName) {} + + std::string getEnv(const char* envName, std::string defVal); + + bool isTracingEnabled(void); + + jaegertracing::Config makeNopTraceConfig(void); + + jaegertracing::samplers::Config getSamplerConfig(void); + + jaegertracing::reporters::Config getReporterConfig(void); + + LogLevel getLoggingLevel(void); + + std::unique_ptr getLogger(void); + + jaegertracing::Config getTraceConfig(void); + + private: + std::string name; + }; +} + +#endif diff --git a/src/tracelib.cpp b/src/tracelib.cpp index 2ab6941..ae8717d 100644 --- a/src/tracelib.cpp +++ b/src/tracelib.cpp @@ -16,13 +16,100 @@ */ #include "tracelibcpp.hpp" +#include "config.hpp" -#include +using namespace tracelibcpp; + +std::string ConfMaker::getEnv(const char* envName, std::string defVal) +{ + const char *ev = getenv(envName); + if (!ev) + return defVal; + return std::string(ev); +} + +bool ConfMaker::isTracingEnabled() +{ + std::string envValue = getEnv(TRACING_ENABLED_ENV, "false"); + if (envValue == "1" || boost::iequals(envValue, "true")) + return true; + else + return false; +} + +jaegertracing::Config ConfMaker::makeNopTraceConfig() +{ + return jaegertracing::Config(true, + jaegertracing::samplers::Config("const", 0)); +} + +jaegertracing::samplers::Config ConfMaker::getSamplerConfig() +{ + std::string samplerType = getEnv(JAEGER_SAMPLER_TYPE_ENV, "const"); + // Use value 0.001 as default param, same way as jaeger does it + double param = atof(getEnv(JAEGER_SAMPLER_PARAM_ENV, "0.001").c_str()); + return jaegertracing::samplers::Config(samplerType, param); +} + +jaegertracing::reporters::Config ConfMaker::getReporterConfig() +{ + std::string agentHostPort = getEnv(JAEGER_AGENT_ADDR_ENV, jaegertracing::reporters::Config::kDefaultLocalAgentHostPort); + + if (agentHostPort.find(':') == std::string::npos) + agentHostPort += ":6831"; + + return jaegertracing::reporters::Config( + 0, std::chrono::seconds(0), // use jaeger defaults + getLoggingLevel() == LOG_ALL, // log spans + agentHostPort + ); +} + +LogLevel ConfMaker::getLoggingLevel() +{ + std::string logLevel = getEnv(JAEGER_LOG_LEVEL_ENV, "error"); + if (boost::iequals(logLevel, "all")) + return LOG_ALL; + else if (boost::iequals(logLevel, "error")) + return LOG_ERR; + else + return LOG_NONE; +} + +std::unique_ptr ConfMaker::getLogger() +{ + switch (getLoggingLevel()) + { + case LOG_ALL: + case LOG_ERR: + return jaegertracing::logging::consoleLogger(); + break; + default: + return jaegertracing::logging::nullLogger(); + } +} + +jaegertracing::Config ConfMaker::getTraceConfig() +{ + if (!isTracingEnabled()) + return makeNopTraceConfig(); + auto sampler = getSamplerConfig(); + auto reporter = getReporterConfig(); + return jaegertracing::Config(false, sampler, reporter); +} std::shared_ptr tracelibcpp::createTracer(std::string serviceName) { - auto config = jaegertracing::Config(true, - jaegertracing::samplers::Config("const", 0)); - return jaegertracing::Tracer::make(serviceName, config, jaegertracing::logging::consoleLogger()); + auto cm = ConfMaker(serviceName); + auto config = cm.getTraceConfig(); + try { + return jaegertracing::Tracer::make(serviceName, config, cm.getLogger()); + } catch (std::exception& e) + { + if (cm.getLoggingLevel() != LOG_NONE) + std::cerr << "Cannot create tracer: " << e.what() << std::endl; + return jaegertracing::Tracer::make(serviceName, cm.makeNopTraceConfig()); + } } + diff --git a/tst/testcreate.cpp b/tst/testcreate.cpp index 6529c18..7ab5a94 100644 --- a/tst/testcreate.cpp +++ b/tst/testcreate.cpp @@ -19,11 +19,145 @@ #include #include "tracelibcpp.hpp" +#include "config.hpp" using namespace testing; using namespace tracelibcpp; +class ConfMakerTest: public ::testing::Test +{ +public: + ConfMaker cm; + ConfMakerTest(): + cm(ConfMaker("testservice")) + { + } + ~ConfMakerTest() + { + unsetenv(JAEGER_SAMPLER_TYPE_ENV); + unsetenv(TRACING_ENABLED_ENV); + unsetenv(JAEGER_SAMPLER_PARAM_ENV); + unsetenv(JAEGER_AGENT_ADDR_ENV); + unsetenv(JAEGER_LOG_LEVEL_ENV); + } +}; + +TEST_F(ConfMakerTest, TestEnvReadingNonExisingReturnsDefaultValue) +{ + EXPECT_THAT("foobar", StrEq(cm.getEnv("nonexistent", "foobar"))); +} + +TEST_F(ConfMakerTest, TestEnvReadingReturnsEnvironmentValValue) +{ + setenv("FOO", "BAR", 1); + auto val = cm.getEnv("FOO", "foobar"); + unsetenv("FOO"); + EXPECT_THAT("BAR", StrEq(val)); +} + +TEST_F(ConfMakerTest, TestTracingIsDisabledByDefault) +{ + EXPECT_FALSE(cm.isTracingEnabled()); +} + +TEST_F(ConfMakerTest, TestTracingCanBeEnabledWithEnvValTrue) +{ + setenv(TRACING_ENABLED_ENV, "true", 1); + EXPECT_TRUE(cm.isTracingEnabled()); + setenv(TRACING_ENABLED_ENV, "TRUE", 1); + EXPECT_TRUE(cm.isTracingEnabled()); +} + +TEST_F(ConfMakerTest, TestTracingCanBeEnabledWithEnvValOne) +{ + setenv(TRACING_ENABLED_ENV, "1", 1); + EXPECT_TRUE(cm.isTracingEnabled()); +} + +TEST_F(ConfMakerTest, TestTracingEnabledWithUnknownValuesResultsDisabled) +{ + setenv(TRACING_ENABLED_ENV, "0", 1); + EXPECT_FALSE(cm.isTracingEnabled()); + setenv(TRACING_ENABLED_ENV, "off", 1); + EXPECT_FALSE(cm.isTracingEnabled()); +} + +TEST_F(ConfMakerTest, TestThatByDefaultConstSamplerIsCreated) +{ + auto samplerconf = cm.getSamplerConfig(); + EXPECT_THAT("const", StrEq(samplerconf.type())); + EXPECT_EQ(0.001, samplerconf.param()); +} + +TEST_F(ConfMakerTest, TestThatSamplerTypeCanBeDefined) +{ + setenv(JAEGER_SAMPLER_TYPE_ENV, "probabilistic", 1); + auto samplerconf = cm.getSamplerConfig(); + EXPECT_THAT("probabilistic", StrEq(samplerconf.type())); + EXPECT_EQ(0.001, samplerconf.param()); +} + +TEST_F(ConfMakerTest, TestThatSamplerParamCanBeDefined) +{ + setenv(JAEGER_SAMPLER_PARAM_ENV, "0.01", 1); + setenv(JAEGER_SAMPLER_TYPE_ENV, "probabilistic", 1); + auto samplerconf = cm.getSamplerConfig(); + EXPECT_EQ(0.01, samplerconf.param()); +} + +TEST_F(ConfMakerTest, TestThatReporterAgentAddrDefaultsToLocalhost) +{ + auto reporterConf = cm.getReporterConfig(); + EXPECT_THAT(reporterConf.localAgentHostPort(), StrEq("127.0.0.1:6831")); +} + +TEST_F(ConfMakerTest, TestThatReporterAgentAddrCanBeDefined) +{ + setenv(JAEGER_AGENT_ADDR_ENV, "1.1.1.1:1111", 1); + auto reporterConf = cm.getReporterConfig(); + EXPECT_THAT(reporterConf.localAgentHostPort(), StrEq("1.1.1.1:1111")); +} + +TEST_F(ConfMakerTest, TestThatIfAgentPortIsNotGivenDefaultIsUsed) +{ + setenv(JAEGER_AGENT_ADDR_ENV, "1.1.1.1", 1); + auto reporterConf = cm.getReporterConfig(); + EXPECT_THAT(reporterConf.localAgentHostPort(), StrEq("1.1.1.1:6831")); +} + +TEST_F(ConfMakerTest, TestThatLoggingDefaultIsErr) +{ + EXPECT_EQ(LOG_ERR, cm.getLoggingLevel()); +} + +TEST_F(ConfMakerTest, TestThatLoggingLevelCanBeConfigured) +{ + setenv(JAEGER_LOG_LEVEL_ENV, "all", 1); + EXPECT_EQ(LOG_ALL, cm.getLoggingLevel()); + + setenv(JAEGER_LOG_LEVEL_ENV, "error", 1); + EXPECT_EQ(LOG_ERR, cm.getLoggingLevel()); + + setenv(JAEGER_LOG_LEVEL_ENV, "none", 1); + EXPECT_EQ(LOG_NONE, cm.getLoggingLevel()); +} + +TEST_F(ConfMakerTest, TestThatByDefaultDisabledConfigIsCreated) +{ + auto conf = cm.getTraceConfig(); + EXPECT_TRUE(conf.disabled()); +} + +TEST_F(ConfMakerTest, TestThatIfTracerCreationFailsNoopTracerIsReturned) +{ + setenv(TRACING_ENABLED_ENV, "1", 1); + setenv(JAEGER_AGENT_ADDR_ENV, "foobar.invalid", 1); // invalid address causes an exception + auto tracer = createTracer("foo"); + EXPECT_THAT(tracer, NotNull()); +} + TEST(CreateTest, TestThatTraceCreateReturnsANewInstance) { auto tracer = createTracer("foo"); EXPECT_THAT(tracer, NotNull()); } + -- 2.16.6