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

Enhance existing http example to support w3c trace context propagation #727

Merged
merged 14 commits into from
May 8, 2021
6 changes: 3 additions & 3 deletions examples/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cc_binary(
name = "example_http_client",
srcs = [
"client.cc",
"tracer_common.hpp",
"tracer_common.h",
],
# TODO: Move copts/linkopts for static CURL usage into shared bzl file.
copts = [
Expand Down Expand Up @@ -30,8 +30,8 @@ cc_binary(
name = "example_http_server",
srcs = [
"server.cc",
"server.hpp",
"tracer_common.hpp",
"server.h",
"tracer_common.h",
],
deps = [
"//api",
Expand Down
41 changes: 30 additions & 11 deletions examples/http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
This is a simple example that demonstrates tracing an HTTP request from client to server. The example shows several aspects of tracing such as:

* Using the `TracerProvider`
* Using the `GlobalPropagator`
* Span Attributes
* Span Events
* Using the ostream exporter
* Nested spans (TBD)
* W3c Trace Context Propagation (TBD)
* W3C Trace Context Propagation

### Running the example

Expand Down Expand Up @@ -38,18 +39,20 @@ This is a simple example that demonstrates tracing an HTTP request from client t
* Client console

```console

{
name : /helloworld
trace_id : 15c7ca1993b536085f4097f2818a7be4
span_id : 7d9136e4eb4cb59d
trace_id : baa922bc0da6f79d46373371f4416463
span_id : 83bed4da7a01267d
tracestate :
parent_span_id: 0000000000000000
start : 1617075613395810300
duration : 1901100
start : 1620287026111457000
duration : 5164400
description :
span kind : Client
status : Unset
attributes :
http.header.Date: Tue, 30 Mar 2021 03:40:13 GMT
http.header.Date: Thu, 06 May 2021 07:43:46 GMT
http.header.Content-Length: 0
http.status_code: 200
http.method: GET
Expand All @@ -58,30 +61,46 @@ This is a simple example that demonstrates tracing an HTTP request from client t
http.header.Connection: keep-alive
http.scheme: http
http.url: h**p://localhost:8800/helloworld
events :
links :

}
```

* Server console

```console

{
name : /helloworld
trace_id : bfa611a4bbb8b1871ef6a222d6a0f4dd
span_id : 19e3cda7df63c9b9
parent_span_id: 0000000000000000
start : 1617075522491536300
trace_id : baa922bc0da6f79d46373371f4416463
span_id : c3e7e23042eb670e
tracestate :
parent_span_id: 83bed4da7a01267d
start : 1620287026115443300
duration : 50700
description :
span kind : Server
status : Unset
attributes :
http.header.Traceparent: 00-baa922bc0da6f79d46373371f4416463-83bed4da7a01267d-01
http.header.Accept: */*
http.request_content_length: 0
http.header.Host: localhost:8800
http.scheme: http
http.client_ip: 127.0.0.1:44616
http.client_ip: 127.0.0.1:50792
http.method: GET
net.host.port: 8800
http.server_name: localhost
events :
{
name : Processing request
timestamp : 1620287026115464000
attributes :
}
links :

}
```

As seen from example above, `trace_id` and `parent_span_id` is propagated from client to server.
11 changes: 9 additions & 2 deletions examples/http/client.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "opentelemetry/ext/http/client/http_client_factory.h"
#include "opentelemetry/ext/http/common/url_parser.h"
#include "tracer_common.hpp"
#include "tracer_common.h"

namespace
{
Expand All @@ -23,7 +23,14 @@ void sendRequest(const std::string &url)
options);
auto scope = get_tracer("http-client")->WithActiveSpan(span);

opentelemetry::ext::http::client::Result result = http_client->Get(url);
// inject current context into http header
auto current_ctx = opentelemetry::context::RuntimeContext::GetCurrent();
HttpTextMapCarrier<opentelemetry::ext::http::client::Headers> carrier;
auto prop = opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator();
prop->Inject(carrier, current_ctx);

// send http request
opentelemetry::ext::http::client::Result result = http_client->Get(url, carrier.headers_);
if (result)
{
// set span attributes
Expand Down
14 changes: 12 additions & 2 deletions examples/http/server.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include "server.hpp"
#include "tracer_common.hpp"
#include "server.h"
#include "tracer_common.h"

#include <iostream>
#include <thread>
Expand All @@ -19,6 +19,15 @@ class RequestHandler : public HTTP_SERVER_NS::HttpRequestCallback
options.kind = opentelemetry::trace::SpanKind::kServer; // server
std::string span_name = request.uri;

// extract context from http header
const HttpTextMapCarrier<std::map<std::string, std::string>> carrier(
(std::map<std::string, std::string> &)request.headers);
auto prop = opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator();
auto current_ctx = opentelemetry::context::RuntimeContext::GetCurrent();
auto new_context = prop->Extract(carrier, current_ctx);
options.parent = GetSpanFromContext(new_context)->GetContext();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this shouldn't be strictly necessary, as Extract sets the span as the currently active span in the context?

Copy link
Member Author

@lalitb lalitb May 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pyohannes - Extract would return a new context with the span created with extracted span_context, and won't make it active. This is as per the specs :

Returns a new Context derived from the Context passed as argument.


// start span with parent context extracted from http header
auto span = get_tracer("http-server")
->StartSpan(span_name,
{{"http.server_name", server_name},
Expand All @@ -30,6 +39,7 @@ class RequestHandler : public HTTP_SERVER_NS::HttpRequestCallback
options);

auto scope = get_tracer("http_server")->WithActiveSpan(span);

for (auto &kv : request.headers)
{
span->SetAttribute("http.header." + std::string(kv.first.data()), kv.second);
Expand Down
50 changes: 50 additions & 0 deletions examples/http/server.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once
#include <atomic>
#include <string>
#include "opentelemetry/ext/http/server/http_server.h"

namespace
{

class HttpServer : public HTTP_SERVER_NS::HttpRequestCallback
{

protected:
HTTP_SERVER_NS::HttpServer server_;
std::string server_url_;
uint16_t port_;
std::atomic<bool> is_running_{false};

public:
HttpServer(std::string server_name = "test_server", uint16_t port = 8800) : port_(port)
{
server_.setServerName(server_name);
server_.setKeepalive(false);
}

void AddHandler(std::string path, HTTP_SERVER_NS::HttpRequestCallback *request_handler)
{
server_.addHandler(path, *request_handler);
}

void Start()
{
if (!is_running_.exchange(true))
{
server_.addListeningPort(port_);
server_.start();
}
}

void Stop()
{
if (is_running_.exchange(false))
{
server_.stop();
}
}

~HttpServer() { Stop(); }
};

} // namespace
47 changes: 0 additions & 47 deletions examples/http/server.hpp

This file was deleted.

95 changes: 95 additions & 0 deletions examples/http/tracer_common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#pragma once
#include "opentelemetry/exporters/ostream/span_exporter.h"
#include "opentelemetry/sdk/trace/simple_processor.h"
#include "opentelemetry/sdk/trace/tracer_provider.h"
#include "opentelemetry/trace/provider.h"

#include "opentelemetry/context/propagation/global_propagator.h"
#include "opentelemetry/context/propagation/text_map_propagator.h"
#include "opentelemetry/trace/propagation/http_trace_context.h"

#include <cstring>
#include <iostream>
#include <vector>
#include "opentelemetry/ext/http/client/http_client.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can eventually generalize some of this, since the same / similar logic would be necessary for gRPC metadata. I like how it was handled in dapr examples:

https://docs.dapr.io/developing-applications/building-blocks/observability/w3c-tracing/w3c-tracing-howto/

Copy link
Member Author

@lalitb lalitb May 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should eventually reuse part of the code for both HTTP and grpc.

#include "opentelemetry/nostd/shared_ptr.h"

namespace
{
// TBD - This function be removed once #723 is merged
inline nostd::shared_ptr<opentelemetry::trace::Span> GetSpanFromContext(
const opentelemetry::context::Context &context)
{
opentelemetry::context::ContextValue span = context.GetValue(opentelemetry::trace::kSpanKey);
if (nostd::holds_alternative<nostd::shared_ptr<opentelemetry::trace::Span>>(span))
{
return nostd::get<nostd::shared_ptr<opentelemetry::trace::Span>>(span);
}
static nostd::shared_ptr<opentelemetry::trace::Span> invalid_span{
new opentelemetry::trace::DefaultSpan(opentelemetry::trace::SpanContext::GetInvalid())};
return invalid_span;
}

template <typename T>
class HttpTextMapCarrier : public opentelemetry::context::propagation::TextMapCarrier
{
public:
HttpTextMapCarrier<T>(T &headers) : headers_(headers) {}
HttpTextMapCarrier() = default;
virtual nostd::string_view Get(nostd::string_view key) const noexcept override
{
std::string key_to_compare = key.data();
// Header's first letter seems to be automatically capitaliazed by our test http-server, so
// compare accordingly.
if (key == opentelemetry::trace::propagation::kTraceParent)
{
key_to_compare = "Traceparent";
}
else if (key == opentelemetry::trace::propagation::kTraceState)
{
key_to_compare == "Tracestate";
}
auto it = headers_.find(key_to_compare);
if (it != headers_.end())
{
return it->second;
}
return "";
}

virtual void Set(nostd::string_view key, nostd::string_view value) noexcept override
{
headers_.insert(std::pair<std::string, std::string>(std::string(key), std::string(value)));
}

T headers_;
};

void initTracer()
{
auto exporter = std::unique_ptr<sdktrace::SpanExporter>(
new opentelemetry::exporter::trace::OStreamSpanExporter);
auto processor = std::unique_ptr<sdktrace::SpanProcessor>(
new sdktrace::SimpleSpanProcessor(std::move(exporter)));
std::vector<std::unique_ptr<sdktrace::SpanProcessor>> processors;
processors.push_back(std::move(processor));
// Default is an always-on sampler.
auto context = std::make_shared<sdktrace::TracerContext>(std::move(processors));
auto provider = nostd::shared_ptr<opentelemetry::trace::TracerProvider>(
new sdktrace::TracerProvider(context));
// Set the global trace provider
opentelemetry::trace::Provider::SetTracerProvider(provider);

// set global propagator
opentelemetry::context::propagation::GlobalTextMapPropagator::SetGlobalPropagator(
nostd::shared_ptr<opentelemetry::context::propagation::TextMapPropagator>(
new opentelemetry::trace::propagation::HttpTraceContext()));
}

nostd::shared_ptr<opentelemetry::trace::Tracer> get_tracer(std::string tracer_name)
{
auto provider = opentelemetry::trace::Provider::GetTracerProvider();
return provider->GetTracer(tracer_name);
}

} // namespace
32 changes: 0 additions & 32 deletions examples/http/tracer_common.hpp

This file was deleted.