Add http client/server example (#632)
This commit is contained in:
parent
63372257f9
commit
179a7f4047
|
@ -6,3 +6,4 @@ add_subdirectory(simple)
|
|||
add_subdirectory(batch)
|
||||
add_subdirectory(metrics_simple)
|
||||
add_subdirectory(multithreaded)
|
||||
add_subdirectory(http)
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
find_package(CURL)
|
||||
|
||||
if(NOT CURL_FOUND)
|
||||
message(WARNING "Skipping http client/server example build: CURL not found")
|
||||
else()
|
||||
include_directories(${CMAKE_SOURCE_DIR}/exporters/ostream/include
|
||||
${CMAKE_SOURCE_DIR}/ext/include ${CMAKE_SOURCE_DIR/})
|
||||
|
||||
add_executable(http_client client.cc)
|
||||
add_executable(http_server server.cc)
|
||||
|
||||
target_link_libraries(
|
||||
http_client
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${CORE_RUNTIME_LIBS}
|
||||
opentelemetry_trace
|
||||
http_client_curl
|
||||
opentelemetry_exporter_ostream_span
|
||||
${CURL_LIBRARIES})
|
||||
|
||||
target_link_libraries(
|
||||
http_server ${CMAKE_THREAD_LIBS_INIT} ${CORE_RUNTIME_LIBS}
|
||||
opentelemetry_trace http_client_curl opentelemetry_exporter_ostream_span)
|
||||
endif()
|
|
@ -0,0 +1,87 @@
|
|||
# OpenTelemetry C++ Example
|
||||
|
||||
## HTTP
|
||||
|
||||
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`
|
||||
* Span Attributes
|
||||
* Span Events
|
||||
* Using the ostream exporter
|
||||
* Nested spans (TBD)
|
||||
* W3c Trace Context Propagation (TBD)
|
||||
|
||||
### Running the example
|
||||
|
||||
1. The example uses HTTP server and client provided as part of this repo:
|
||||
* [HTTP Client](https://github.com/open-telemetry/opentelemetry-cpp/tree/main/ext/include/opentelemetry/ext/http/client)
|
||||
* [HTTP Server](https://github.com/open-telemetry/opentelemetry-cpp/tree/main/ext/include/opentelemetry/ext/http/server)
|
||||
|
||||
2. Build and Deploy the opentelementry-cpp as described in [INSTALL.md](../../INSTALL.md)
|
||||
|
||||
3. Start the server from the `examples/http` directory
|
||||
|
||||
```console
|
||||
$ http_server 8800
|
||||
Server is running..Press ctrl-c to exit...
|
||||
```
|
||||
|
||||
4. In a separate terminal window, run the client to make a single request:
|
||||
|
||||
```console
|
||||
$ ./http_client 8800
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
5. You should see console exporter output for both the client and server sessions.
|
||||
* Client console
|
||||
|
||||
```console
|
||||
{
|
||||
name : /helloworld
|
||||
trace_id : 15c7ca1993b536085f4097f2818a7be4
|
||||
span_id : 7d9136e4eb4cb59d
|
||||
parent_span_id: 0000000000000000
|
||||
start : 1617075613395810300
|
||||
duration : 1901100
|
||||
description :
|
||||
span kind : Client
|
||||
status : Unset
|
||||
attributes :
|
||||
http.header.Date: Tue, 30 Mar 2021 03:40:13 GMT
|
||||
http.header.Content-Length: 0
|
||||
http.status_code: 200
|
||||
http.method: GET
|
||||
http.header.Host: localhost
|
||||
http.header.Content-Type: text/plain
|
||||
http.header.Connection: keep-alive
|
||||
http.scheme: http
|
||||
http.url: h**p://localhost:8800/helloworld
|
||||
}
|
||||
```
|
||||
|
||||
* Server console
|
||||
|
||||
```console
|
||||
{
|
||||
name : /helloworld
|
||||
trace_id : bfa611a4bbb8b1871ef6a222d6a0f4dd
|
||||
span_id : 19e3cda7df63c9b9
|
||||
parent_span_id: 0000000000000000
|
||||
start : 1617075522491536300
|
||||
duration : 50700
|
||||
description :
|
||||
span kind : Server
|
||||
status : Unset
|
||||
attributes :
|
||||
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.method: GET
|
||||
net.host.port: 8800
|
||||
http.server_name: localhost
|
||||
}
|
||||
```
|
|
@ -0,0 +1,78 @@
|
|||
#include "opentelemetry/ext/http/client/http_client_factory.h"
|
||||
#include "opentelemetry/ext/http/common/url_parser.h"
|
||||
#include "tracer_common.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void sendRequest(const std::string &url)
|
||||
{
|
||||
auto http_client = opentelemetry::ext::http::client::HttpClientFactory::CreateSync();
|
||||
|
||||
// start active span
|
||||
opentelemetry::trace::StartSpanOptions options;
|
||||
options.kind = opentelemetry::trace::SpanKind::kClient; // client
|
||||
opentelemetry::ext::http::common::UrlParser url_parser(url);
|
||||
|
||||
std::string span_name = url_parser.path_;
|
||||
auto span = get_tracer("http-client")
|
||||
->StartSpan(span_name,
|
||||
{{"http.url", url_parser.url_},
|
||||
{"http.scheme", url_parser.scheme_},
|
||||
{"http.method", "GET"}},
|
||||
options);
|
||||
auto scope = get_tracer("http-client")->WithActiveSpan(span);
|
||||
|
||||
opentelemetry::ext::http::client::Result result = http_client->Get(url);
|
||||
if (result)
|
||||
{
|
||||
// set span attributes
|
||||
auto status_code = result.GetResponse().GetStatusCode();
|
||||
span->SetAttribute("http.status_code", status_code);
|
||||
result.GetResponse().ForEachHeader([&span](opentelemetry::nostd::string_view header_name,
|
||||
opentelemetry::nostd::string_view header_value) {
|
||||
span->SetAttribute("http.header." + std::string(header_name.data()), header_value);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (status_code >= 400)
|
||||
{
|
||||
span->SetStatus(opentelemetry::trace::StatusCode::kError);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
span->SetStatus(opentelemetry::trace::StatusCode::kError,
|
||||
"Response Status :" +
|
||||
std::to_string(static_cast<typename std::underlying_type<
|
||||
opentelemetry::ext::http::client::SessionState>::type>(
|
||||
result.GetSessionState())));
|
||||
}
|
||||
// end span and export data
|
||||
span->End();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
initTracer();
|
||||
constexpr char default_host[] = "localhost";
|
||||
constexpr char default_path[] = "/helloworld";
|
||||
constexpr uint16_t default_port = 8800;
|
||||
uint16_t port;
|
||||
|
||||
// The port the validation service listens to can be specified via the command line.
|
||||
if (argc > 1)
|
||||
{
|
||||
port = atoi(argv[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
port = default_port;
|
||||
}
|
||||
|
||||
std::string url = "http://" + std::string(default_host) + ":" + std::to_string(port) +
|
||||
std::string(default_path);
|
||||
sendRequest(url);
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
#include "server.hpp"
|
||||
#include "tracer_common.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
namespace
|
||||
{
|
||||
uint16_t server_port = 8800;
|
||||
constexpr char server_name[] = "localhost";
|
||||
|
||||
class RequestHandler : public HTTP_SERVER_NS::HttpRequestCallback
|
||||
{
|
||||
public:
|
||||
virtual int onHttpRequest(HTTP_SERVER_NS::HttpRequest const &request,
|
||||
HTTP_SERVER_NS::HttpResponse &response) override
|
||||
{
|
||||
opentelemetry::trace::StartSpanOptions options;
|
||||
options.kind = opentelemetry::trace::SpanKind::kServer; // server
|
||||
std::string span_name = request.uri;
|
||||
|
||||
auto span = get_tracer("http-server")
|
||||
->StartSpan(span_name,
|
||||
{{"http.server_name", server_name},
|
||||
{"net.host.port", server_port},
|
||||
{"http.method", request.method},
|
||||
{"http.scheme", "http"},
|
||||
{"http.request_content_length", request.content.length()},
|
||||
{"http.client_ip", request.client}},
|
||||
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);
|
||||
}
|
||||
if (request.uri == "/helloworld")
|
||||
{
|
||||
span->AddEvent("Processing request");
|
||||
response.headers[HTTP_SERVER_NS::CONTENT_TYPE] = HTTP_SERVER_NS::CONTENT_TYPE_TEXT;
|
||||
span->End();
|
||||
return 200;
|
||||
}
|
||||
span->End();
|
||||
return 404;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
initTracer();
|
||||
uint16_t port;
|
||||
|
||||
// The port the validation service listens to can be specified via the command line.
|
||||
if (argc > 1)
|
||||
{
|
||||
server_port = atoi(argv[1]);
|
||||
}
|
||||
|
||||
HttpServer http_server(server_name, server_port);
|
||||
RequestHandler req_handler;
|
||||
http_server.AddHandler("/helloworld", &req_handler);
|
||||
auto root_span = get_tracer("http_server")->StartSpan(__func__);
|
||||
opentelemetry::trace::Scope scope(root_span);
|
||||
http_server.Start();
|
||||
std::cout << "Server is running..Press ctrl-c to exit...\n";
|
||||
while (1)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::seconds(100));
|
||||
}
|
||||
http_server.Stop();
|
||||
root_span->End();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
#include "opentelemetry/ext/http/server/http_server.h"
|
||||
#include<string>
|
||||
#include<atomic>
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
#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 <iostream>
|
||||
|
||||
namespace {
|
||||
|
||||
void initTracer() {
|
||||
auto exporter = std::unique_ptr<sdktrace::SpanExporter>(
|
||||
new opentelemetry::exporter::trace::OStreamSpanExporter);
|
||||
auto processor = std::shared_ptr<sdktrace::SpanProcessor>(
|
||||
new sdktrace::SimpleSpanProcessor(std::move(exporter)));
|
||||
auto provider = nostd::shared_ptr<opentelemetry::trace::TracerProvider>(
|
||||
new sdktrace::TracerProvider(processor, opentelemetry::sdk::resource::Resource::Create({}),
|
||||
std::make_shared<opentelemetry::sdk::trace::AlwaysOnSampler>()));
|
||||
// Set the global trace provider
|
||||
opentelemetry::trace::Provider::SetTracerProvider(provider);
|
||||
}
|
||||
|
||||
nostd::shared_ptr<opentelemetry::trace::Tracer> get_tracer(std::string tracer_name)
|
||||
{
|
||||
auto provider = opentelemetry::trace::Provider::GetTracerProvider();
|
||||
return provider->GetTracer(tracer_name);
|
||||
}
|
||||
|
||||
}
|
|
@ -107,15 +107,12 @@ private:
|
|||
void printAttributes(std::unordered_map<std::string, sdkcommon::OwnedAttributeValue> map)
|
||||
{
|
||||
size_t size = map.size();
|
||||
size_t i = 1;
|
||||
// size_t i = 1;
|
||||
for (auto kv : map)
|
||||
{
|
||||
sout_ << kv.first << ": ";
|
||||
sout_ << "\t" << kv.first << ": ";
|
||||
print_value(kv.second);
|
||||
|
||||
if (i != size)
|
||||
sout_ << ", ";
|
||||
i++;
|
||||
sout_ << std::endl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -69,10 +69,10 @@ sdktrace::ExportResult OStreamSpanExporter::Export(
|
|||
<< "\n duration : " << span->GetDuration().count()
|
||||
<< "\n description : " << span->GetDescription()
|
||||
<< "\n span kind : " << span->GetSpanKind()
|
||||
<< "\n status : " << statusMap[int(span->GetStatus())]
|
||||
<< "\n attributes : ";
|
||||
<< "\n status : " << statusMap[int(span->GetStatus())] << "\n attributes : "
|
||||
<< "\n";
|
||||
printAttributes(span->GetAttributes());
|
||||
sout_ << "\n}\n";
|
||||
sout_ << "}\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -145,7 +145,8 @@ TEST(OStreamSpanExporter, PrintChangedSpanCout)
|
|||
" description : Test Description\n"
|
||||
" span kind : Client\n"
|
||||
" status : Ok\n"
|
||||
" attributes : attr1: string\n"
|
||||
" attributes : \n"
|
||||
"\tattr1: string\n"
|
||||
"}\n";
|
||||
ASSERT_EQ(stdoutOutput.str(), expectedOutput);
|
||||
}
|
||||
|
@ -208,7 +209,8 @@ TEST(OStreamSpanExporter, PrintChangedSpanCerr)
|
|||
" description : Test Description\n"
|
||||
" span kind : Consumer\n"
|
||||
" status : Ok\n"
|
||||
" attributes : attr1: [0,1,0]\n"
|
||||
" attributes : \n"
|
||||
"\tattr1: [0,1,0]\n"
|
||||
"}\n";
|
||||
ASSERT_EQ(stdcerrOutput.str(), expectedOutput);
|
||||
}
|
||||
|
@ -270,7 +272,8 @@ TEST(OStreamSpanExporter, PrintChangedSpanClog)
|
|||
" description : Test Description\n"
|
||||
" span kind : Internal\n"
|
||||
" status : Ok\n"
|
||||
" attributes : attr1: [1,2,3]\n"
|
||||
" attributes : \n"
|
||||
"\tattr1: [1,2,3]\n"
|
||||
"}\n";
|
||||
ASSERT_EQ(stdclogOutput.str(), expectedOutput);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
|
Loading…
Reference in New Issue