Currency Service
This service provides functionality to convert amounts between different currencies.
Traces
Initializing Tracing
The OpenTelemetry SDK is initialized from main using the initTracer function defined in tracer_common.h
void initTracer() { auto exporter = opentelemetry::exporter::otlp::OtlpGrpcExporterFactory::Create(); auto processor = opentelemetry::sdk::trace::SimpleSpanProcessorFactory::Create(std::move(exporter)); std::vector<std::unique_ptr<opentelemetry::sdk::trace::SpanProcessor>> processors; processors.push_back(std::move(processor)); std::shared_ptr<opentelemetry::sdk::trace::TracerContext> context = opentelemetry::sdk::trace::TracerContextFactory::Create(std::move(processors)); std::shared_ptr<opentelemetry::trace::TracerProvider> provider = opentelemetry::sdk::trace::TracerProviderFactory::Create(context); // Set the global trace provider opentelemetry::trace::Provider::SetTracerProvider(provider); // set global propagator opentelemetry::context::propagation::GlobalTextMapPropagator::SetGlobalPropagator( opentelemetry::nostd::shared_ptr<opentelemetry::context::propagation::TextMapPropagator>( new opentelemetry::trace::propagation::HttpTraceContext())); } Create new spans
New spans can be created and started using Tracer->StartSpan("spanName", attributes, options). After a span is created you need to start and put it into active context using Tracer->WithActiveSpan(span). You can find an example of this in the Convert function.
std::string span_name = "CurrencyService/Convert"; auto span = get_tracer("currency")->StartSpan(span_name, {{SemanticConventions::kRpcSystem, "grpc"}, {SemanticConventions::kRpcService, "oteldemo.CurrencyService"}, {SemanticConventions::kRpcMethod, "Convert"}, {SemanticConventions::kRpcGrpcStatusCode, 0}}, options); auto scope = get_tracer("currency")->WithActiveSpan(span); Adding attributes to spans
You can add an attribute to a span using Span->SetAttribute(key, value).
span->SetAttribute("app.currency.conversion.from", from_code); span->SetAttribute("app.currency.conversion.to", to_code); Add span events
Adding span events is accomplished using Span->AddEvent(name).
span->AddEvent("Conversion successful, response sent back"); Set span status
Make sure to set your span status to Ok, or Error accordingly. You can do this using Span->SetStatus(status)
span->SetStatus(StatusCode::kOk); Tracing context propagation
In C++ propagation is not automatically handled. You need to extract it from the caller and inject the propagation context into subsequent spans. The GrpcServerCarrier class defines a method to extract context from inbound gRPC requests which is leveraged in the service call implementations.
The GrpcServerCarrier class is defined in tracer_common.h as follows:
class GrpcServerCarrier : public opentelemetry::context::propagation::TextMapCarrier { public: GrpcServerCarrier(ServerContext *context) : context_(context) {} GrpcServerCarrier() = default; virtual opentelemetry::nostd::string_view Get( opentelemetry::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(opentelemetry::nostd::string_view key, opentelemetry::nostd::string_view value) noexcept override { // Not required for server } ServerContext *context_; }; This class is leveraged in the Convert method to extract context and create a StartSpanOptions object to contain the right context which is used when creating new spans.
StartSpanOptions options; options.kind = SpanKind::kServer; GrpcServerCarrier carrier(context); auto prop = context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); auto current_ctx = context::RuntimeContext::GetCurrent(); auto new_context = prop->Extract(carrier, current_ctx); options.parent = GetSpan(new_context)->GetContext(); Metrics
Initializing Metrics
The OpenTelemetry MeterProvider is initialized from main() using the initMeter() function defined in meter_common.h.
void initMeter() { // Build MetricExporter otlp_exporter::OtlpGrpcMetricExporterOptions otlpOptions; auto exporter = otlp_exporter::OtlpGrpcMetricExporterFactory::Create(otlpOptions); // Build MeterProvider and Reader metric_sdk::PeriodicExportingMetricReaderOptions options; std::unique_ptr<metric_sdk::MetricReader> reader{ new metric_sdk::PeriodicExportingMetricReader(std::move(exporter), options) }; auto provider = std::shared_ptr<metrics_api::MeterProvider>(new metric_sdk::MeterProvider()); auto p = std::static_pointer_cast<metric_sdk::MeterProvider>(provider); p->AddMetricReader(std::move(reader)); metrics_api::Provider::SetMeterProvider(provider); } Starting IntCounter
A global currency_counter variable is created at main() calling the function initIntCounter() defined in meter_common.h.
nostd::unique_ptr<metrics_api::Counter<uint64_t>> initIntCounter() { std::string counter_name = name + "_counter"; auto provider = metrics_api::Provider::GetMeterProvider(); nostd::shared_ptr<metrics_api::Meter> meter = provider->GetMeter(name, version); auto int_counter = meter->CreateUInt64Counter(counter_name); return int_counter; } Counting currency conversion requests
The method CurrencyCounter() is implemented as follows:
void CurrencyCounter(const std::string& currency_code) { std::map<std::string, std::string> labels = { {"currency_code", currency_code} }; auto labelkv = common::KeyValueIterableView<decltype(labels)>{ labels }; currency_counter->Add(1, labelkv); } Every time the function Convert() is called, the currency code received as to_code is used to count the conversions.
CurrencyCounter(to_code); Logs
The OpenTelemetry LoggerProvider is initialized from main() using the initLogger() function defined in logger_common.h.
void initLogger() { otlp::OtlpGrpcLogRecordExporterOptions loggerOptions; auto exporter = otlp::OtlpGrpcLogRecordExporterFactory::Create(loggerOptions); auto processor = logs_sdk::SimpleLogRecordProcessorFactory::Create(std::move(exporter)); std::vector<std::unique_ptr<logs_sdk::LogRecordProcessor>> processors; processors.push_back(std::move(processor)); auto context = logs_sdk::LoggerContextFactory::Create(std::move(processors)); std::shared_ptr<logs::LoggerProvider> provider = logs_sdk::LoggerProviderFactory::Create(std::move(context)); opentelemetry::logs::Provider::SetLoggerProvider(provider); } Using the LoggerProvider
The initialized Logger Provider is called from main in server.cpp:
logger = getLogger(name); It assigns the logger to a local variable called logger:
nostd::shared_ptr<opentelemetry::logs::Logger> logger; Which is then used throughout the code whenever we need to log a line:
logger->Info(std::string(__func__) + " conversion successful"); Feedback
Was this page helpful?
Thank you. Your feedback is appreciated!
Please let us know how we can improve this page. Your feedback is appreciated!