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 <roni.riska@nokia.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6a8d101..e44fb55 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -42,7 +42,7 @@
 
 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 @@
 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 @@
 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 @@
 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 <boost/algorithm/string.hpp>
+#include <jaegertracing/Tracer.h>
+
+#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<jaegertracing::logging::Logger> 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 <jaegertracing/Tracer.h>
+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<jaegertracing::logging::Logger> 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<opentracing::Tracer> 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 <gmock/gmock.h>
 
 #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());
 }
+