Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connect tracer with external application's logging facilities #150

Merged
merged 10 commits into from
Jan 23, 2021
2 changes: 2 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ cc_library(
"src/encoder.h",
"src/limiter.cpp",
"src/limiter.h",
"src/logger.cpp",
"src/logger.h",
"src/opentracing_external.cpp",
"src/propagation.cpp",
"src/propagation.h",
Expand Down
30 changes: 30 additions & 0 deletions include/datadog/opentracing.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <opentracing/tracer.h>

#include <cmath>
#include <iostream>
#include <map>
#include <set>

Expand All @@ -12,6 +13,17 @@ namespace ot = opentracing;
namespace datadog {
namespace opentracing {

// Log levels used within the datadog tracer. The numberic values are arbitrary,
// and the logging function is responsible for mapping these levels to the
// application-specific logger's levels.
enum class LogLevel {
debug = 1,
info = 2,
error = 3,
};

using LogFunc = std::function<void(LogLevel, ot::string_view)>;

// The type of headers that are used for propagating distributed traces.
// B3 headers only support 64 bit trace IDs.
enum class PropagationStyle {
Expand Down Expand Up @@ -87,6 +99,24 @@ struct TracerOptions {
// If no scheme is set in the URL, a path to a UNIX domain socket is assumed.
// Can also be set by the environment variable DD_TRACE_AGENT_URL.
std::string agent_url = "";
// A logging function that is called by the tracer when noteworthy events occur.
// The default value uses std::cerr, and applications can inject their own logging function.
LogFunc log_func = [](LogLevel level, ot::string_view message) {
switch (level) {
case LogLevel::debug:
std::cerr << "debug: " << message << std::endl;
break;
case LogLevel::info:
std::cerr << "info: " << message << std::endl;
break;
case LogLevel::error:
std::cerr << "error: " << message << std::endl;
break;
default:
std::cerr << "<unknown level>: " << message << std::endl;
break;
}
};
};

// TraceEncoder exposes the data required to encode and submit traces to the
Expand Down
62 changes: 62 additions & 0 deletions src/logger.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "logger.h"

namespace datadog {
namespace opentracing {

namespace {

std::string format_message(uint64_t trace_id, ot::string_view message) {
return std::string("[trace_id: ") + std::to_string(trace_id) + std::string("] ") +
std::string(message);
}

std::string format_message(uint64_t trace_id, uint64_t span_id, ot::string_view message) {
return std::string("[trace_id: ") + std::to_string(trace_id) + std::string(", span_id: ") +
std::to_string(span_id) + std::string("] ") + std::string(message);
}

} // namespace

void StandardLogger::Log(LogLevel level, ot::string_view message) const noexcept {
log_func_(level, ot::string_view{message});
}

void StandardLogger::Log(LogLevel level, uint64_t trace_id, ot::string_view message) const
noexcept {
log_func_(level, ot::string_view{format_message(trace_id, message)});
}

void StandardLogger::Log(LogLevel level, uint64_t trace_id, uint64_t span_id,
ot::string_view message) const noexcept {
log_func_(level, format_message(trace_id, span_id, message));
}

void VerboseLogger::Log(LogLevel level, ot::string_view message) const noexcept {
log_func_(level, message);
}

void VerboseLogger::Log(LogLevel level, uint64_t trace_id, ot::string_view message) const
noexcept {
log_func_(level, format_message(trace_id, message));
}

void VerboseLogger::Log(LogLevel level, uint64_t trace_id, uint64_t span_id,
ot::string_view message) const noexcept {
log_func_(level, format_message(trace_id, span_id, message));
}

void VerboseLogger::Trace(ot::string_view message) const noexcept {
log_func_(LogLevel::debug, message);
}

void VerboseLogger::Trace(uint64_t trace_id, ot::string_view message) const noexcept {
log_func_(LogLevel::debug, format_message(trace_id, message));
}

void VerboseLogger::Trace(uint64_t trace_id, uint64_t span_id, ot::string_view message) const
noexcept {
log_func_(LogLevel::debug, format_message(trace_id, span_id, message));
}

} // namespace opentracing
} // namespace datadog
55 changes: 55 additions & 0 deletions src/logger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#ifndef DD_OPENTRACING_LOGGER_H
#define DD_OPENTRACING_LOGGER_H

#include "datadog/opentracing.h"

namespace datadog {
namespace opentracing {

class Logger {
public:
virtual void Log(LogLevel level, ot::string_view message) const noexcept = 0;
virtual void Log(LogLevel level, uint64_t trace_id, ot::string_view message) const noexcept = 0;
virtual void Log(LogLevel level, uint64_t trace_id, uint64_t span_id,
ot::string_view message) const noexcept = 0;
virtual void Trace(ot::string_view message) const noexcept = 0;
virtual void Trace(uint64_t trace_id, ot::string_view message) const noexcept = 0;
virtual void Trace(uint64_t trace_id, uint64_t span_id, ot::string_view message) const
noexcept = 0;

protected:
Logger(LogFunc log_func) : log_func_(log_func) {}
virtual ~Logger() = default;
LogFunc log_func_;
};

// The standard logger provides stub implementations of Trace methods, that reduces the
// performance hit when this level of detail is disabled.
class StandardLogger final : public Logger {
public:
StandardLogger(LogFunc log_func) : Logger(log_func) {}
void Log(LogLevel level, ot::string_view message) const noexcept override;
void Log(LogLevel level, uint64_t trace_id, ot::string_view message) const noexcept override;
void Log(LogLevel level, uint64_t trace_id, uint64_t span_id, ot::string_view message) const
noexcept override;
void Trace(ot::string_view) const noexcept override {}
void Trace(uint64_t, ot::string_view) const noexcept override {}
void Trace(uint64_t, uint64_t, ot::string_view) const noexcept override {}
};

class VerboseLogger final : public Logger {
public:
VerboseLogger(LogFunc log_func) : Logger(log_func) {}
void Log(LogLevel level, ot::string_view message) const noexcept override;
void Log(LogLevel level, uint64_t trace_id, ot::string_view message) const noexcept override;
void Log(LogLevel level, uint64_t trace_id, uint64_t span_id, ot::string_view message) const
noexcept override;
void Trace(ot::string_view message) const noexcept override;
void Trace(uint64_t trace_id, ot::string_view message) const noexcept override;
void Trace(uint64_t trace_id, uint64_t span_id, ot::string_view message) const noexcept override;
};

} // namespace opentracing
} // namespace datadog

#endif // DD_OPENTRACING_LOGGER_H
5 changes: 2 additions & 3 deletions src/opentracing_external.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ std::tuple<std::shared_ptr<ot::Tracer>, std::shared_ptr<TraceEncoder>> makeTrace
TracerOptions opts = maybe_options.value();

auto sampler = std::make_shared<RulesSampler>();
auto xwriter = std::make_shared<ExternalWriter>(sampler);
auto encoder = xwriter->encoder();
std::shared_ptr<Writer> writer = xwriter;
auto writer = std::make_shared<ExternalWriter>(sampler);
auto encoder = writer->encoder();
return std::tuple<std::shared_ptr<ot::Tracer>, std::shared_ptr<TraceEncoder>>{
std::shared_ptr<ot::Tracer>{new Tracer{opts, writer, sampler}}, encoder};
}
Expand Down
40 changes: 27 additions & 13 deletions src/propagation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,27 +117,35 @@ OptionalSamplingPriority asSamplingPriority(int i) {
return std::make_unique<SamplingPriority>(static_cast<SamplingPriority>(i));
}

SpanContext::SpanContext(uint64_t id, uint64_t trace_id, std::string origin,
SpanContext::SpanContext(std::shared_ptr<const Logger> logger, uint64_t id, uint64_t trace_id,
std::string origin,
std::unordered_map<std::string, std::string> &&baggage)
: id_(id), trace_id_(trace_id), origin_(origin), baggage_(std::move(baggage)) {}
: logger_(std::move(logger)),
id_(id),
trace_id_(trace_id),
origin_(origin),
baggage_(std::move(baggage)) {}

SpanContext SpanContext::NginxOpenTracingCompatibilityHackSpanContext(
uint64_t id, uint64_t trace_id, std::unordered_map<std::string, std::string> &&baggage) {
SpanContext c = SpanContext{id, trace_id, "", std::move(baggage)};
std::shared_ptr<const Logger> logger, uint64_t id, uint64_t trace_id,
std::unordered_map<std::string, std::string> &&baggage) {
SpanContext c = SpanContext{logger, id, trace_id, "", std::move(baggage)};
c.nginx_opentracing_compatibility_hack_ = true;
return c;
}

SpanContext::SpanContext(SpanContext &&other)
: nginx_opentracing_compatibility_hack_(other.nginx_opentracing_compatibility_hack_),
propagated_sampling_priority_(std::move(other.propagated_sampling_priority_)),
logger_(other.logger_),
id_(other.id_),
trace_id_(other.trace_id_),
propagated_sampling_priority_(std::move(other.propagated_sampling_priority_)),
origin_(other.origin_),
baggage_(std::move(other.baggage_)) {}

SpanContext &SpanContext::operator=(SpanContext &&other) {
std::lock_guard<std::mutex> lock{mutex_};
logger_ = other.logger_;
id_ = other.id_;
trace_id_ = other.trace_id_;
origin_ = other.origin_;
Expand All @@ -148,7 +156,8 @@ SpanContext &SpanContext::operator=(SpanContext &&other) {
}

bool SpanContext::operator==(const SpanContext &other) const {
if (id_ != other.id_ || trace_id_ != other.trace_id_ || baggage_ != other.baggage_ ||
if (logger_ != other.logger_ || id_ != other.id_ || trace_id_ != other.trace_id_ ||
baggage_ != other.baggage_ ||
nginx_opentracing_compatibility_hack_ != other.nginx_opentracing_compatibility_hack_) {
return false;
}
Expand Down Expand Up @@ -214,7 +223,7 @@ std::string SpanContext::baggageItem(ot::string_view key) const {
SpanContext SpanContext::withId(uint64_t id) const {
std::lock_guard<std::mutex> lock{mutex_};
auto baggage = baggage_; // (Shallow) copy baggage.
SpanContext context{id, trace_id_, origin_, std::move(baggage)};
SpanContext context{logger_, id, trace_id_, origin_, std::move(baggage)};
if (propagated_sampling_priority_ != nullptr) {
context.propagated_sampling_priority_.reset(
new SamplingPriority(*propagated_sampling_priority_));
Expand Down Expand Up @@ -318,7 +327,8 @@ ot::expected<void> SpanContext::serialize(const ot::TextMapWriter &writer,
return result;
}

ot::expected<std::unique_ptr<ot::SpanContext>> SpanContext::deserialize(std::istream &reader) try {
ot::expected<std::unique_ptr<ot::SpanContext>> SpanContext::deserialize(
std::shared_ptr<const Logger> logger, std::istream &reader) try {
// check istream state
if (!reader.good()) {
return ot::make_unexpected(std::make_error_code(std::errc::io_error));
Expand Down Expand Up @@ -373,7 +383,8 @@ ot::expected<std::unique_ptr<ot::SpanContext>> SpanContext::deserialize(std::ist
j.at(json_baggage_key).get_to(baggage);
}

auto context = std::make_unique<SpanContext>(parent_id, trace_id, origin, std::move(baggage));
auto context =
std::make_unique<SpanContext>(logger, parent_id, trace_id, origin, std::move(baggage));
context->propagated_sampling_priority_ = std::move(sampling_priority);
return std::unique_ptr<ot::SpanContext>(std::move(context));
} catch (const json::parse_error &) {
Expand All @@ -385,10 +396,11 @@ ot::expected<std::unique_ptr<ot::SpanContext>> SpanContext::deserialize(std::ist
}

ot::expected<std::unique_ptr<ot::SpanContext>> SpanContext::deserialize(
const ot::TextMapReader &reader, std::set<PropagationStyle> styles) try {
std::shared_ptr<const Logger> logger, const ot::TextMapReader &reader,
std::set<PropagationStyle> styles) try {
std::unique_ptr<ot::SpanContext> context = nullptr;
for (PropagationStyle style : styles) {
auto result = SpanContext::deserialize(reader, propagation_headers[style]);
auto result = SpanContext::deserialize(logger, reader, propagation_headers[style]);
if (!result) {
return ot::make_unexpected(result.error());
}
Expand All @@ -408,7 +420,8 @@ ot::expected<std::unique_ptr<ot::SpanContext>> SpanContext::deserialize(
}

ot::expected<std::unique_ptr<ot::SpanContext>> SpanContext::deserialize(
const ot::TextMapReader &reader, const HeadersImpl &headers_impl) {
std::shared_ptr<const Logger> logger, const ot::TextMapReader &reader,
const HeadersImpl &headers_impl) {
uint64_t trace_id, parent_id;
OptionalSamplingPriority sampling_priority = nullptr;
std::string origin;
Expand Down Expand Up @@ -464,7 +477,8 @@ ot::expected<std::unique_ptr<ot::SpanContext>> SpanContext::deserialize(
// Origin header should only be set if sampling priority is also set.
return ot::make_unexpected(ot::span_context_corrupted_error);
}
auto context = std::make_unique<SpanContext>(parent_id, trace_id, origin, std::move(baggage));
auto context =
std::make_unique<SpanContext>(logger, parent_id, trace_id, origin, std::move(baggage));
context->propagated_sampling_priority_ = std::move(sampling_priority);
return std::unique_ptr<ot::SpanContext>(std::move(context));
}
Expand Down
22 changes: 14 additions & 8 deletions src/propagation.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <mutex>
#include <set>
#include <unordered_map>
#include "logger.h"

namespace ot = opentracing;

Expand All @@ -21,6 +22,7 @@ const ot::string_view baggage_prefix = "ot-baggage-";
std::vector<ot::string_view> getPropagationHeaderNames(const std::set<PropagationStyle> &styles,
bool prioritySamplingEnabled);

class Tracer;
class SpanBuffer;
struct HeadersImpl;

Expand All @@ -47,12 +49,13 @@ OptionalSamplingPriority asSamplingPriority(int i);

class SpanContext : public ot::SpanContext {
public:
SpanContext(uint64_t id, uint64_t trace_id, std::string origin,
std::unordered_map<std::string, std::string> &&baggage);
SpanContext(std::shared_ptr<const Logger> logger, uint64_t id, uint64_t trace_id,
std::string origin, std::unordered_map<std::string, std::string> &&baggage);

// Enables a hack, see the comment below on nginx_opentracing_compatibility_hack_.
static SpanContext NginxOpenTracingCompatibilityHackSpanContext(
uint64_t id, uint64_t trace_id, std::unordered_map<std::string, std::string> &&baggage);
std::shared_ptr<const Logger> logger, uint64_t id, uint64_t trace_id,
std::unordered_map<std::string, std::string> &&baggage);

SpanContext(SpanContext &&other);
SpanContext &operator=(SpanContext &&other);
Expand All @@ -78,9 +81,11 @@ class SpanContext : public ot::SpanContext {
SpanContext withId(uint64_t id) const;

// Returns a new context from the given reader.
static ot::expected<std::unique_ptr<ot::SpanContext>> deserialize(std::istream &reader);
static ot::expected<std::unique_ptr<ot::SpanContext>> deserialize(
const ot::TextMapReader &reader, std::set<PropagationStyle> styles);
std::shared_ptr<const Logger> tracer, std::istream &reader);
static ot::expected<std::unique_ptr<ot::SpanContext>> deserialize(
std::shared_ptr<const Logger> tracer, const ot::TextMapReader &reader,
std::set<PropagationStyle> styles);

uint64_t id() const;
uint64_t traceId() const;
Expand All @@ -93,7 +98,8 @@ class SpanContext : public ot::SpanContext {

private:
static ot::expected<std::unique_ptr<ot::SpanContext>> deserialize(
const ot::TextMapReader &reader, const HeadersImpl &headers_impl);
std::shared_ptr<const Logger> tracer, const ot::TextMapReader &reader,
const HeadersImpl &headers_impl);
ot::expected<void> serialize(const ot::TextMapWriter &writer,
const std::shared_ptr<SpanBuffer> pending_traces,
const HeadersImpl &headers_impl,
Expand All @@ -117,10 +123,10 @@ class SpanContext : public ot::SpanContext {
// make it more of a pain to do and less obvious what's happening.
bool nginx_opentracing_compatibility_hack_ = false;

OptionalSamplingPriority propagated_sampling_priority_ = nullptr;

std::shared_ptr<const Logger> logger_;
uint64_t id_;
uint64_t trace_id_;
OptionalSamplingPriority propagated_sampling_priority_ = nullptr;
std::string origin_;

mutable std::mutex mutex_;
Expand Down
Loading