diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e0689f9a78..f7eb276016 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,5 +1,6 @@ if(WITH_OTLP) add_subdirectory(otlp) + add_subdirectory(grpc) endif() if(WITH_JAEGER) add_subdirectory(jaeger) diff --git a/examples/grpc/CMakeLists.txt b/examples/grpc/CMakeLists.txt new file mode 100644 index 0000000000..2b43cf18f6 --- /dev/null +++ b/examples/grpc/CMakeLists.txt @@ -0,0 +1,48 @@ +# Proto file +get_filename_component(proto_file "./protos/messages.proto" ABSOLUTE) +get_filename_component(proto_file_path "${proto_file}" PATH) + +message("PATH:${proto_file_path}:${proto_file}") +# Generated sources +set(example_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/messages.pb.cc") +set(example_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/messages.pb.h") +set(example_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/messages.grpc.pb.cc") +set(example_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/messages.grpc.pb.h") + +add_custom_command( + OUTPUT "${example_proto_srcs}" "${example_proto_hdrs}" "${example_grpc_srcs}" + "${example_grpc_hdrs}" + COMMAND + ${PROTOBUF_PROTOC_EXECUTABLE} ARGS "--grpc_out=${CMAKE_CURRENT_BINARY_DIR}" + "--cpp_out=${CMAKE_CURRENT_BINARY_DIR}" "--proto_path=${proto_file_path}" + --plugin=protoc-gen-grpc="${gRPC_CPP_PLUGIN_EXECUTABLE}" "${proto_file}") +# DEPENDS "${proto_file}") + +# hw_grpc_proto +add_library(example_grpc_proto ${example_grpc_srcs} ${example_grpc_hdrs} + ${example_proto_srcs} ${example_proto_hdrs}) + +include_directories( + ${CMAKE_SOURCE_DIR}/exporters/ostream/include ${CMAKE_SOURCE_DIR}/ext/include + ${CMAKE_SOURCE_DIR}/api/include/ ${CMAKE_SOURCE_DIR/}) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +if(TARGET protobuf::libprotobuf) + target_link_libraries(example_grpc_proto gRPC::grpc++ protobuf::libprotobuf) +else() + target_include_directories(example_grpc_proto ${Protobuf_INCLUDE_DIRS}) + target_link_libraries(example_grpc_proto ${Protobuf_LIBRARIES}) +endif() + +foreach(_target client server) + add_executable(${_target} "${_target}.cpp") + target_link_libraries( + ${_target} + example_grpc_proto + protobuf::libprotobuf + gRPC::grpc++ + gRPC::grpc++_reflection + opentelemetry_trace + opentelemetry_exporter_ostream_span) +endforeach() diff --git a/examples/grpc/README.md b/examples/grpc/README.md new file mode 100644 index 0000000000..d964501aea --- /dev/null +++ b/examples/grpc/README.md @@ -0,0 +1,99 @@ +# OpenTelemetry C++ Example + +## gRPC + +This is a simple example that demonstrates tracing a gRPC request from client to server. There is an experimental directory in this example - the code within has been commented out to prevent any conflicts. The example shows several aspects of tracing such as: + +* Using the `TracerProvider` +* Implementing the TextMapCarrier +* Context injection/extraction +* Span Attributes +* Span Semantic Conventions +* Using the ostream exporter +* Nested spans +* W3c Trace Context Propagation (Very soon!) + +### Running the example + +1. The example uses gRPC C++ as well as Google's protocol buffers. Make sure you have installed both + of these packages on your system, in such a way that CMake would know how to find them with this command: + + ``find_package(gRPC)`` + +2. Build and Deploy the opentelementry-cpp as described in [INSTALL.md](../../INSTALL.md). Building the project will build all of the examples + and create new folders containing their executables within the 'build' directory NOT the 'examples' directory. + +3. Start the server from your `build/examples/grpc` directory. Both the server and client are configured to use 8800 as the default port, + but if you would like to use another port, you can specify that as an argument. + + ```console + $ ./server [port_num] + Server listening on port: 0.0.0.0:8800 + ``` + +4. In a separate terminal window, run the client to make a single request: + + ```console + $ ./client [port_num] + ... + ``` + +5. You should see console exporter output for both the client and server sessions. + * Client console + + ```console + { + name : GreeterClient/Greet + trace_id : f5d16f8399be0d2c6b39d992634ffdbb + span_id : 9c79a2dd744d7d2d + tracestate : + parent_span_id: 0000000000000000 + start : 1622603339918985700 + duration : 4960500 + description : + span kind : Client + status : Ok + attributes : + rpc.grpc.status_code: 0 + net.peer.port: 8080 + net.peer.ip: 0.0.0.0 + rpc.method: Greet + rpc.service: grpc-example.GreetService + rpc.system: grpc + events : + } + ``` + + * Server console + + ```console + { + name : GreeterService/Greet + trace_id : f5d16f8399be0d2c6b39d992634ffdbb + span_id : 1e8a7d2d46e08573 + tracestate : + parent_span_id: 9c79a2dd744d7d2d + start : 1622603339923163800 + duration : 76400 + description : + span kind : Server + status : Ok + attributes : + rpc.grpc.status_code: 0 + rpc.method: Greet + rpc.service: GreeterService + rpc.system: grpc + events : + { + name : Processing client attributes + timestamp : 1622603339923180800 + attributes : + } + { + name : Response sent to client + timestamp : 1622603339923233700 + attributes : + } + links : + } + ``` diff --git a/examples/grpc/client.cpp b/examples/grpc/client.cpp new file mode 100644 index 0000000000..5ebdbf617d --- /dev/null +++ b/examples/grpc/client.cpp @@ -0,0 +1,109 @@ +#include "tracer_common.h" +#include +#include +#include + +#include + +#include "messages.grpc.pb.h" + +using grpc::Channel; +using grpc::ClientContext; +using grpc::ClientReader; +using grpc::Status; + +using grpc_example::Greeter; +using grpc_example::GreetRequest; +using grpc_example::GreetResponse; + + +namespace +{ + +class GreeterClient +{ +public: + GreeterClient(std::shared_ptr channel) : stub_(Greeter::NewStub(channel)) {} + + std::string Greet(std::string ip, uint16_t port) + { + // Build gRPC Context objects and protobuf message containers + GreetRequest request; + GreetResponse response; + ClientContext context; + request.set_request("Nice to meet you!"); + + opentelemetry::trace::StartSpanOptions options; + options.kind = opentelemetry::trace::SpanKind::kClient; + + std::string span_name = "GreeterClient/Greet"; + auto span = get_tracer("grpc")->StartSpan(span_name, + {{"rpc.system", "grpc"}, + {"rpc.service", "grpc-example.GreetService"}, + {"rpc.method", "Greet"}, + {"net.peer.ip", ip}, + {"net.peer.port", port}}, + options); + + auto scope = get_tracer("grpc-client")->WithActiveSpan(span); + + // inject current context to grpc metadata + auto current_ctx = opentelemetry::context::RuntimeContext::GetCurrent(); + GrpcClientCarrier carrier(&context); + auto prop = opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); + prop->Inject(carrier, current_ctx); + + // Send request to server + Status status = stub_->Greet(&context, request, &response); + if (status.ok()) + { + span->SetStatus(opentelemetry::trace::StatusCode::kOk); + span->SetAttribute("rpc.grpc.status_code", status.error_code()); + // Make sure to end your spans! + span->End(); + return response.response(); + } + else + { + std::cout << status.error_code() << ": " << status.error_message() << std::endl; + span->SetStatus(opentelemetry::trace::StatusCode::kError); + span->SetAttribute("rpc.grpc.status_code", status.error_code()); + // Make sure to end your spans! + span->End(); + return "RPC failed"; + } + } + +private: + std::unique_ptr stub_; +}; // GreeterClient class + +void RunClient(uint16_t port) +{ + GreeterClient greeter( + grpc::CreateChannel("0.0.0.0:" + std::to_string(port), grpc::InsecureChannelCredentials())); + std::string response = greeter.Greet("0.0.0.0", port); + std::cout << "grpc_server says: " << response << std::endl; +} +} // namespace + +int main(int argc, char **argv) +{ + initTracer(); + // set global propagator + opentelemetry::context::propagation::GlobalTextMapPropagator::SetGlobalPropagator( + nostd::shared_ptr( + new opentelemetry::trace::propagation::HttpTraceContext())); + constexpr uint16_t default_port = 8800; + uint16_t port; + if (argc > 1) + { + port = atoi(argv[1]); + } + else + { + port = default_port; + } + RunClient(port); + return 0; +} diff --git a/examples/grpc/protos/messages.proto b/examples/grpc/protos/messages.proto new file mode 100644 index 0000000000..e0d69959bf --- /dev/null +++ b/examples/grpc/protos/messages.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package grpc_example; + +service Greeter { + rpc Greet(GreetRequest) returns (GreetResponse) {} +} + +message GreetRequest { + string request = 1; +} + +message GreetResponse { + string response = 1; +} diff --git a/examples/grpc/server.cpp b/examples/grpc/server.cpp new file mode 100644 index 0000000000..167fc08966 --- /dev/null +++ b/examples/grpc/server.cpp @@ -0,0 +1,114 @@ +#include "messages.grpc.pb.h" +#include "tracer_common.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using grpc::Server; +using grpc::ServerBuilder; +using grpc::ServerContext; +using grpc::ServerWriter; +using grpc::Status; + +using grpc_example::Greeter; +using grpc_example::GreetRequest; +using grpc_example::GreetResponse; + +using Span = opentelemetry::trace::Span; +using SpanContext = opentelemetry::trace::SpanContext; + +namespace +{ +class GreeterServer final : public Greeter::Service +{ +public: + Status Greet(ServerContext *context, + const GreetRequest *request, + GreetResponse *response) override + { + for( auto elem: context->client_metadata()) { + std::cout << "ELEM: " << elem.first << " " << elem.second << "\n"; + } + + // Create a SpanOptions object and set the kind to Server to inform OpenTel. + opentelemetry::trace::StartSpanOptions options; + options.kind = opentelemetry::trace::SpanKind::kServer; + + // extract context from grpc metadata + GrpcServerCarrier carrier(context); + + auto prop = opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); + auto current_ctx = opentelemetry::context::RuntimeContext::GetCurrent(); + auto new_context = prop->Extract(carrier, current_ctx); + options.parent = opentelemetry::trace::propagation::GetSpan(new_context)->GetContext(); + + std::string span_name = "GreeterService/Greet"; + auto span = get_tracer("grpc") + ->StartSpan(span_name, + {{"rpc.system", "grpc"}, + {"rpc.service", "GreeterService"}, + {"rpc.method", "Greet"}, + {"rpc.grpc.status_code", 0}}, + options); + auto scope = get_tracer("grpc")->WithActiveSpan(span); + + // Fetch and parse whatever HTTP headers we can from the gRPC request. + span->AddEvent("Processing client attributes"); + + std::string req = request->request(); + std::cout << std::endl << "grpc_client says: " << req << std::endl; + std::string message = "The pleasure is mine."; + // Send response to client + response->set_response(message); + span->AddEvent("Response sent to client"); + + span->SetStatus(opentelemetry::trace::StatusCode::kOk); + // Make sure to end your spans! + span->End(); + return Status::OK; + } +}; // GreeterServer class + +void RunServer(uint16_t port) +{ + std::string address("0.0.0.0:" + std::to_string(port)); + GreeterServer service; + ServerBuilder builder; + + builder.RegisterService(&service); + builder.AddListeningPort(address, grpc::InsecureServerCredentials()); + + std::unique_ptr server(builder.BuildAndStart()); + std::cout << "Server listening on port: " << address << std::endl; + server->Wait(); + server->Shutdown(); +} +} // namespace + +int main(int argc, char **argv) +{ + initTracer(); + constexpr uint16_t default_port = 8800; + uint16_t port; + if (argc > 1) + { + port = atoi(argv[1]); + } + else + { + port = default_port; + } + + RunServer(port); + return 0; +} diff --git a/examples/grpc/tracer_common.h b/examples/grpc/tracer_common.h new file mode 100644 index 0000000000..8d1df41ad4 --- /dev/null +++ b/examples/grpc/tracer_common.h @@ -0,0 +1,91 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#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/nostd/shared_ptr.h" +#include "opentelemetry/trace/propagation/http_trace_context.h" + +#include +#include +#include +#include + +using grpc::ClientContext; +using grpc::ServerContext; + +namespace +{ +class GrpcClientCarrier : public opentelemetry::context::propagation::TextMapCarrier +{ +public: + GrpcClientCarrier(ClientContext *context) : context_(context) {} + GrpcClientCarrier() = default; + virtual nostd::string_view Get(nostd::string_view key) const noexcept override { return ""; } + + virtual void Set(nostd::string_view key, nostd::string_view value) noexcept override + { + std::cout << " Client ::: Adding " << key << " " << value << "\n"; + context_->AddMetadata(key.data(), value.data()); + } + + ClientContext *context_; +}; + +class GrpcServerCarrier : public opentelemetry::context::propagation::TextMapCarrier +{ +public: + GrpcServerCarrier(ServerContext *context) : context_(context) {} + GrpcServerCarrier() = default; + virtual nostd::string_view Get(nostd::string_view key) const noexcept override + { + auto it = context_->client_metadata().find(key.data()); + if (it != context_->client_metadata().end()) + { + return it->second.data(); + } + return ""; + } + + virtual void Set(nostd::string_view key, nostd::string_view value) noexcept override + { + // Not required for server + } + + ServerContext *context_; +}; + +void initTracer() +{ + auto exporter = std::unique_ptr( + new opentelemetry::exporter::trace::OStreamSpanExporter); + auto processor = std::unique_ptr( + new sdktrace::SimpleSpanProcessor(std::move(exporter))); + std::vector> processors; + processors.push_back(std::move(processor)); + // Default is an always-on sampler. + auto context = std::make_shared(std::move(processors)); + auto provider = nostd::shared_ptr( + 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( + new opentelemetry::trace::propagation::HttpTraceContext())); +} + +nostd::shared_ptr get_tracer(std::string tracer_name) +{ + auto provider = opentelemetry::trace::Provider::GetTracerProvider(); + return provider->GetTracer(tracer_name); +} + +} // namespace