Skip to content

Commit

Permalink
Add support for DD_ENV, DD_SERVICE, DD_VERSION and DD_TAGS (#132)
Browse files Browse the repository at this point in the history
  • Loading branch information
cgilmour authored Apr 9, 2020
1 parent 12062a4 commit 0ec6af4
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 39 deletions.
8 changes: 7 additions & 1 deletion include/datadog/opentracing.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct TracerOptions {
// Port on which the Datadog agent is running. Can also be set by the environment variable
// DD_TRACE_AGENT_PORT
uint32_t agent_port = 8126;
// The name of the service being traced.
// The name of the service being traced. Can also be set by the environment variable DD_SERVICE.
// See:
// https://help.datadoghq.com/hc/en-us/articles/115000702546-What-is-the-Difference-Between-Type-Service-Resource-and-Name-
std::string service;
Expand Down Expand Up @@ -76,6 +76,12 @@ struct TracerOptions {
// value for analytics sampling rate. Can also be set by the environment variable
// DD_TRACE_ANALYTICS_SAMPLE_RATE
double analytics_rate = std::nan("");
// Tags that are applied to all spans reported by this tracer. Can also be set by the environment
// variable DD_TAGS.
std::map<std::string, std::string> tags = {};
// The version of the overall application being traced. Can also be set by the environment
// variable DD_VERSION.
std::string version = "";
};

// TraceEncoder exposes the data required to encode and submit traces to the
Expand Down
1 change: 1 addition & 0 deletions include/datadog/tags.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const std::string resource_name = "resource.name";
const std::string analytics_event = "analytics.event";
const std::string manual_keep = "manual.keep";
const std::string manual_drop = "manual.drop";
const std::string version = "version";

} // namespace tags
} // namespace datadog
Expand Down
8 changes: 7 additions & 1 deletion src/tracer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,20 @@ std::unique_ptr<ot::Span> Tracer::StartSpanWithOptions(ot::string_view operation
opts_.service, opts_.type, operation_name, operation_name,
opts_.operation_name_override}};
bool is_trace_root = parent_id == 0;
if (!opts_.version.empty()) {
span->SetTag(datadog::tags::version, opts_.version);
}
for (auto &tag : opts_.tags) {
span->SetTag(tag.first, tag.second);
}
for (auto &tag : options.tags) {
if (tag.first == ::ot::ext::sampling_priority && span->getSamplingPriority() != nullptr) {
// Do not apply this tag if sampling priority is already assigned.
continue;
}
span->SetTag(tag.first, tag.second);
}
if (is_trace_root && opts_.environment != "") {
if (is_trace_root && !opts_.environment.empty()) {
span->SetTag(tags::environment, opts_.environment);
}
return span;
Expand Down
19 changes: 13 additions & 6 deletions src/tracer_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,10 @@ ot::expected<TracerOptions> optionsFromConfig(const char *configuration,
return ot::make_unexpected(std::make_error_code(std::errc::invalid_argument));
}
try {
// Mandatory config.
if (config.find("service") == config.end()) {
error_message = "configuration argument 'service' is missing";
return ot::make_unexpected(std::make_error_code(std::errc::invalid_argument));
// Mandatory config, but may be set via environment.
if (config.find("service") != config.end()) {
config.at("service").get_to(options.service);
}
config.at("service").get_to(options.service);
// Optional.
if (config.find("agent_host") != config.end()) {
config.at("agent_host").get_to(options.agent_host);
Expand Down Expand Up @@ -91,7 +89,16 @@ ot::expected<TracerOptions> optionsFromConfig(const char *configuration,
error_message = maybe_options.error();
return ot::make_unexpected(std::make_error_code(std::errc::invalid_argument));
}
return maybe_options.value();

options = maybe_options.value();
// sanity-check for final option values
if (options.service.empty()) {
error_message =
"tracer option 'service' has not been set via config or DD_SERVICE environment variable";
return ot::make_unexpected(std::make_error_code(std::errc::invalid_argument));
}

return options;
}

// Accepts configuration in JSON format, with the following keys:
Expand Down
101 changes: 96 additions & 5 deletions src/tracer_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,116 @@
#include <iterator>
#include <sstream>

#include <datadog/tags.h>
#include <opentracing/ext/tags.h>

namespace ot = opentracing;

namespace datadog {
namespace opentracing {

// Extracts key-value pairs from a string.
// Duplicates are overwritten. Empty keys are ignored.
// Intended use is for settings tags from DD_TAGS options.
std::map<std::string, std::string> keyvalues(std::string text, char itemsep, char tokensep,
char escape) {
// early-return if empty
if (text.empty()) {
return {};
}

bool esc = false;
std::map<std::string, std::string> kvp;
std::string key;
std::string val;
bool keyfound = false;
auto assignchar = [&](char c) {
if (keyfound) {
val += c;
} else {
key += c;
}
esc = false;
};
auto addkv = [&](std::string key, std::string val) {
if (key.empty()) {
return;
}
if (val.empty()) {
return;
}
kvp[key] = val;
};
for (auto ch : text) {
if (esc) {
assignchar(ch);
continue;
}
if (ch == escape) {
esc = true;
continue;
}
if (ch == tokensep) {
addkv(key, val);
key = "";
val = "";
keyfound = false;
continue;
}
if (ch == itemsep) {
keyfound = true;
continue;
}
assignchar(ch);
}
if (!key.empty()) {
addkv(key, val);
}

return kvp;
}

ot::expected<TracerOptions, const char *> applyTracerOptionsFromEnvironment(
const TracerOptions &input) {
TracerOptions opts = input;

auto agent_host = std::getenv("DD_AGENT_HOST");
if (agent_host != nullptr && std::strlen(agent_host) > 0) {
opts.agent_host = agent_host;
}

auto environment = std::getenv("DD_ENV");
if (environment != nullptr && std::strlen(environment) > 0) {
opts.environment = environment;
}

auto service = std::getenv("DD_SERVICE");
if (service != nullptr && std::strlen(service) > 0) {
opts.service = service;
}

auto version = std::getenv("DD_VERSION");
if (version != nullptr && std::strlen(version) > 0) {
opts.version = version;
}

auto tags = std::getenv("DD_TAGS");
if (tags != nullptr && std::strlen(tags) > 0) {
opts.tags = keyvalues(tags, ':', ',', '\\');
// Special cases for env, version and sampling priority
if (environment != nullptr && std::strlen(environment) > 0 &&
opts.tags.find(datadog::tags::environment) != opts.tags.end()) {
opts.tags.erase(datadog::tags::environment);
}
if (version != nullptr && std::strlen(version) > 0 &&
opts.tags.find(datadog::tags::version) != opts.tags.end()) {
opts.tags.erase(datadog::tags::version);
}
if (opts.tags.find(ot::ext::sampling_priority) != opts.tags.end()) {
opts.tags.erase(ot::ext::sampling_priority);
}
}

auto agent_host = std::getenv("DD_AGENT_HOST");
if (agent_host != nullptr && std::strlen(agent_host) > 0) {
opts.agent_host = agent_host;
}

auto trace_agent_port = std::getenv("DD_TRACE_AGENT_PORT");
if (trace_agent_port != nullptr && std::strlen(trace_agent_port) > 0) {
try {
Expand Down
40 changes: 39 additions & 1 deletion test/tracer_factory_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ TEST_CASE("tracer factory") {
)"}; // Missing service
std::string error = "";
auto result = factory.MakeTracer(input.c_str(), error);
REQUIRE(error == "configuration argument 'service' is missing");
REQUIRE(
error ==
"tracer option 'service' has not been set via config or DD_SERVICE environment variable");
REQUIRE(!result);
REQUIRE(result.error() == std::make_error_code(std::errc::invalid_argument));
}
Expand Down Expand Up @@ -248,6 +250,42 @@ TEST_CASE("tracer factory") {
}

SECTION("injected environment variables") {
SECTION("DD_ENV overrides default") {
::setenv("DD_ENV", "injected-env", 0);
std::string input{R"(
{
"service": "my-service",
"environment": "env"
}
)"};
std::string error = "";
auto result = factory.MakeTracer(input.c_str(), error);
::unsetenv("DD_ENV");

REQUIRE(error == "");
REQUIRE(result->get() != nullptr);
auto tracer = dynamic_cast<MockTracer *>(result->get());
REQUIRE(tracer->opts.service == "my-service");
REQUIRE(tracer->opts.environment == "injected-env");
}

SECTION("DD_SERVICE overrides default") {
::setenv("DD_SERVICE", "injected-service", 0);
std::string input{R"(
{
"service": "my-service"
}
)"};
std::string error = "";
auto result = factory.MakeTracer(input.c_str(), error);
::unsetenv("DD_SERVICE");

REQUIRE(error == "");
REQUIRE(result->get() != nullptr);
auto tracer = dynamic_cast<MockTracer *>(result->get());
REQUIRE(tracer->opts.service == "injected-service");
}

SECTION("DD_AGENT_HOST overrides default") {
::setenv("DD_AGENT_HOST", "injected-hostname", 0);
std::string input{R"(
Expand Down
75 changes: 59 additions & 16 deletions test/tracer_options_test.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "../src/tracer_options.h"

#include <opentracing/ext/tags.h>
#include <catch2/catch.hpp>
using namespace datadog::opentracing;

Expand Down Expand Up @@ -36,6 +37,7 @@ void requireTracerOptionsResultsMatch(const ot::expected<TracerOptions, const ch
} else {
REQUIRE(lhs->analytics_rate == rhs->analytics_rate);
}
REQUIRE(lhs->tags == rhs->tags);
};

TEST_CASE("tracer options from environment variables") {
Expand All @@ -51,26 +53,36 @@ TEST_CASE("tracer options from environment variables") {
{"DD_TRACE_AGENT_PORT", "420"},
{"DD_ENV", "env"},
{"DD_TRACE_SAMPLING_RULES", "rules"},
{"DD_SERVICE", "service"},
{"DD_PROPAGATION_STYLE_EXTRACT", "B3 Datadog"},
{"DD_PROPAGATION_STYLE_INJECT", "Datadog B3"},
{"DD_TRACE_REPORT_HOSTNAME", "true"},
{"DD_TRACE_ANALYTICS_ENABLED", "true"},
{"DD_TRACE_ANALYTICS_SAMPLE_RATE", "0.5"}},
TracerOptions{"host",
420,
"",
"web",
"env",
std::nan(""),
true,
"rules",
1000,
"",
{PropagationStyle::Datadog, PropagationStyle::B3},
{PropagationStyle::Datadog, PropagationStyle::B3},
true,
true,
0.5}},
{"DD_TRACE_ANALYTICS_SAMPLE_RATE", "0.5"},
{"DD_TAGS", "host:my-host-name,region:us-east-1,datacenter:us,partition:5"}},
TracerOptions{
"host",
420,
"service",
"web",
"env",
std::nan(""),
true,
"rules",
1000,
"",
{PropagationStyle::Datadog, PropagationStyle::B3},
{PropagationStyle::Datadog, PropagationStyle::B3},
true,
true,
0.5,
{
{"host", "my-host-name"},
{"region", "us-east-1"},
{"datacenter", "us"},
{"partition", "5"},
},
}},
{{{"DD_PROPAGATION_STYLE_EXTRACT", "Not even a real style"}},
ot::make_unexpected("Value for DD_PROPAGATION_STYLE_EXTRACT is invalid")},
{{{"DD_PROPAGATION_STYLE_INJECT", "Not even a real style"}},
Expand Down Expand Up @@ -100,3 +112,34 @@ TEST_CASE("tracer options from environment variables") {
REQUIRE(unsetenv(env_var.first.c_str()) == 0);
}
}

TEST_CASE("exceptions for DD_TAGS") {
//
TracerOptions input{};
struct TestCase {
std::map<std::string, std::string> environment_variables;
std::map<std::string, std::string> tags;
};

auto test_case = GENERATE(values<TestCase>({
{{{"DD_ENV", "foo"}, {"DD_TAGS", "env:bar"}}, {}},
{{{"DD_TAGS", std::string(ot::ext::sampling_priority) + ":1"}}, {}},
{{{"DD_TAGS", ":,:,:,:"}}, {}}, // repeatedly handles missing keys
{{{"DD_TAGS", "keywithoutvalue:"}}, {}},
{{{"DD_VERSION", "awesomeapp v1.2.3"}, {"DD_TAGS", "version:abcd"}}, {}},
}));

// Setup
for (const auto &env_var : test_case.environment_variables) {
REQUIRE(setenv(env_var.first.c_str(), env_var.second.c_str(), 1) == 0);
}

auto got = applyTracerOptionsFromEnvironment(input);
REQUIRE(got);
REQUIRE(test_case.tags == got->tags);

// Teardown
for (const auto &env_var : test_case.environment_variables) {
REQUIRE(unsetenv(env_var.first.c_str()) == 0);
}
}
Loading

0 comments on commit 0ec6af4

Please sign in to comment.