diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 22f6b9e838..3d5299a444 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -6,3 +6,4 @@ add_subdirectory(simple) add_subdirectory(batch) add_subdirectory(metrics_simple) add_subdirectory(multithreaded) +add_subdirectory(http) diff --git a/examples/http/CMakeLists.txt b/examples/http/CMakeLists.txt new file mode 100644 index 0000000000..52ec677c70 --- /dev/null +++ b/examples/http/CMakeLists.txt @@ -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() diff --git a/examples/http/README.md b/examples/http/README.md new file mode 100644 index 0000000000..5d24e482a8 --- /dev/null +++ b/examples/http/README.md @@ -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 + } + ``` diff --git a/examples/http/client.cc b/examples/http/client.cc new file mode 100644 index 0000000000..64e9722ed3 --- /dev/null +++ b/examples/http/client.cc @@ -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::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); +} diff --git a/examples/http/server.cc b/examples/http/server.cc new file mode 100644 index 0000000000..9c7deb8f79 --- /dev/null +++ b/examples/http/server.cc @@ -0,0 +1,75 @@ +#include "server.hpp" +#include "tracer_common.hpp" + +#include +#include + +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; +} diff --git a/examples/http/server.hpp b/examples/http/server.hpp new file mode 100644 index 0000000000..1dc83788e2 --- /dev/null +++ b/examples/http/server.hpp @@ -0,0 +1,47 @@ +#pragma once +#include "opentelemetry/ext/http/server/http_server.h" +#include +#include + + +namespace { + +class HttpServer : public HTTP_SERVER_NS::HttpRequestCallback +{ + +protected: + HTTP_SERVER_NS::HttpServer server_; + std::string server_url_ ; + uint16_t port_ ; + std::atomic 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(); + } +}; + +} \ No newline at end of file diff --git a/examples/http/tracer_common.hpp b/examples/http/tracer_common.hpp new file mode 100644 index 0000000000..b45102ac9e --- /dev/null +++ b/examples/http/tracer_common.hpp @@ -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 + +namespace { + +void initTracer() { + auto exporter = std::unique_ptr( + new opentelemetry::exporter::trace::OStreamSpanExporter); + auto processor = std::shared_ptr( + new sdktrace::SimpleSpanProcessor(std::move(exporter))); + auto provider = nostd::shared_ptr( + new sdktrace::TracerProvider(processor, opentelemetry::sdk::resource::Resource::Create({}), + std::make_shared())); + // Set the global trace provider + opentelemetry::trace::Provider::SetTracerProvider(provider); +} + +nostd::shared_ptr get_tracer(std::string tracer_name) +{ + auto provider = opentelemetry::trace::Provider::GetTracerProvider(); + return provider->GetTracer(tracer_name); +} + +} \ No newline at end of file diff --git a/exporters/ostream/include/opentelemetry/exporters/ostream/span_exporter.h b/exporters/ostream/include/opentelemetry/exporters/ostream/span_exporter.h index 3caa906203..07d8b63e75 100644 --- a/exporters/ostream/include/opentelemetry/exporters/ostream/span_exporter.h +++ b/exporters/ostream/include/opentelemetry/exporters/ostream/span_exporter.h @@ -107,15 +107,12 @@ class OStreamSpanExporter final : public sdktrace::SpanExporter void printAttributes(std::unordered_map 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; } } }; diff --git a/exporters/ostream/src/span_exporter.cc b/exporters/ostream/src/span_exporter.cc index 31cd029ba5..e3c279abbc 100644 --- a/exporters/ostream/src/span_exporter.cc +++ b/exporters/ostream/src/span_exporter.cc @@ -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"; } } diff --git a/exporters/ostream/test/ostream_span_test.cc b/exporters/ostream/test/ostream_span_test.cc index 071de0777f..a9b33afdd2 100644 --- a/exporters/ostream/test/ostream_span_test.cc +++ b/exporters/ostream/test/ostream_span_test.cc @@ -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); } diff --git a/ext/include/opentelemetry/ext/http/server/socket_tools.h b/ext/include/opentelemetry/ext/http/server/socket_tools.h index 5b8e24db4e..2b31c67e7f 100644 --- a/ext/include/opentelemetry/ext/http/server/socket_tools.h +++ b/ext/include/opentelemetry/ext/http/server/socket_tools.h @@ -14,6 +14,7 @@ #pragma once #include +#include #include #include #include