diff --git a/exe/main.cc b/exe/main.cc index 1b77482437..2cb0b01dbd 100644 --- a/exe/main.cc +++ b/exe/main.cc @@ -19,6 +19,7 @@ #include "motis/data.h" #include "motis/import.h" #include "motis/logging.h" +#include "motis/otel_tracer.h" #include "motis/server.h" #include "./flags.h" @@ -137,6 +138,11 @@ int main(int ac, char** av) { (return_value = set_log_level(std::move(log_lvl)))) { break; } + + if (c.use_otlp()) { + init_opentelemetry(*c.otlp_, motis_version); + } + return_value = server(data{data_path, c}, c, motis_version); } catch (std::exception const& e) { std::cerr << "unable to start server: " << e.what() << "\n"; @@ -293,6 +299,7 @@ int main(int ac, char** av) { break; } + cleanup_opentelemetry_tracer(); google::protobuf::ShutdownProtobufLibrary(); return return_value; } diff --git a/include/motis/config.h b/include/motis/config.h index 19c2bdd4ec..7b13631c27 100644 --- a/include/motis/config.h +++ b/include/motis/config.h @@ -34,6 +34,7 @@ struct config { bool has_elevators() const; bool has_rt_feeds() const; bool use_street_routing() const; + bool use_otlp() const; bool operator==(config const&) const = default; @@ -258,6 +259,21 @@ struct config { limits get_limits() const { return limits_.value_or(limits{}); } std::optional limits_{}; + struct otlp { + bool operator==(otlp const&) const = default; + struct http { + bool operator==(http const&) const = default; + std::string url_{"http://localhost:4318"}; + std::string content_type_{"json"}; + bool use_json_name_{false}; + }; + std::optional http_{}; + + unsigned timeout_{10U}; + headers_t headers_{}; + }; + std::optional otlp_{}; + struct logging { bool operator==(logging const&) const = default; std::optional log_level_{}; diff --git a/include/motis/ctx_data.h b/include/motis/ctx_data.h index c6d0092bfc..ec11fac84c 100644 --- a/include/motis/ctx_data.h +++ b/include/motis/ctx_data.h @@ -1,5 +1,9 @@ #pragma once +#include + +#include "opentelemetry/context/context.h" + #include "ctx/op_id.h" #include "ctx/operation.h" @@ -7,6 +11,8 @@ namespace motis { struct ctx_data { void transition(ctx::transition, ctx::op_id, ctx::op_id) {} + + std::vector otel_context_stack_; }; } // namespace motis \ No newline at end of file diff --git a/include/motis/otel_runtime_context.h b/include/motis/otel_runtime_context.h new file mode 100644 index 0000000000..502f385633 --- /dev/null +++ b/include/motis/otel_runtime_context.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +#include "utl/helpers/algorithm.h" + +#include "opentelemetry/context/context.h" +#include "opentelemetry/context/runtime_context.h" + +#include "ctx/operation.h" + +#include "motis/ctx_data.h" + +namespace motis { + +struct otel_runtime_context_storage + : public opentelemetry::context::RuntimeContextStorage { + opentelemetry::context::Context GetCurrent() noexcept override { + auto const op = ctx::current_op(); + + if (op == nullptr) { + return default_storage_->GetCurrent(); + } + + // How to get stack of contexts from op (operation*) + auto& stack = op->data_.otel_context_stack_; + return stack.empty() ? opentelemetry::context::Context{} : stack.back(); + } + + opentelemetry::nostd::unique_ptr Attach( + opentelemetry::context::Context const& context) noexcept override { + auto const op = ctx::current_op(); + + if (op == nullptr) { + return default_storage_->Attach(context); + } + + op->data_.otel_context_stack_.push_back(context); + return CreateToken(context); + } + + bool Detach(opentelemetry::context::Token& token) noexcept override { + auto const op = ctx::current_op(); + + if (op == nullptr) { + return default_storage_->Detach(token); + } + + auto& stack = op->data_.otel_context_stack_; + + if (utl::find(stack, token) == stack.end()) { + return false; + } + + while (!(token == stack.back())) { + stack.pop_back(); + } + stack.pop_back(); + return true; + } + +private: + std::unique_ptr + default_storage_{std::make_unique< + opentelemetry::context::ThreadLocalContextStorage>()}; +}; + +} // namespace motis \ No newline at end of file diff --git a/include/motis/otel_tracer.h b/include/motis/otel_tracer.h new file mode 100644 index 0000000000..54f51fe36f --- /dev/null +++ b/include/motis/otel_tracer.h @@ -0,0 +1,21 @@ +#pragma once + +#include "opentelemetry/trace/provider.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/tracer.h" + +#include "motis/config.h" + +namespace motis { + +void init_opentelemetry(config::otlp const&, std::string_view const); + +void cleanup_opentelemetry_tracer(); + +inline opentelemetry::nostd::shared_ptr +get_otel_tracer() { + return opentelemetry::trace::Provider::GetTracerProvider()->GetTracer( + "motis"); +} + +} // namespace motis \ No newline at end of file diff --git a/src/config.cc b/src/config.cc index 3834a8357e..cd939d151f 100644 --- a/src/config.cc +++ b/src/config.cc @@ -287,4 +287,8 @@ bool config::use_street_routing() const { street_routing_); } +bool config::use_otlp() const { + return otlp_.has_value() && otlp_->http_.has_value(); +} + } // namespace motis diff --git a/src/otel_tracer.cc b/src/otel_tracer.cc new file mode 100644 index 0000000000..951dd695f7 --- /dev/null +++ b/src/otel_tracer.cc @@ -0,0 +1,94 @@ +#include "motis/otel_tracer.h" + +#include +#include +#include + +#include "utl/verify.h" + +#include "opentelemetry/context/propagation/global_propagator.h" +#include "opentelemetry/context/runtime_context.h" +#include "opentelemetry/exporters/otlp/otlp_http.h" +#include "opentelemetry/exporters/otlp/otlp_http_exporter_factory.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/trace/samplers/always_on_factory.h" +#include "opentelemetry/sdk/trace/simple_processor_factory.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/sdk/trace/tracer_provider_factory.h" +#include "opentelemetry/trace/propagation/http_trace_context.h" + +#include "motis/otel_runtime_context.h" + +namespace motis { + +static std::shared_ptr provider_; + +void init_opentelemetry_tracer( + opentelemetry::sdk::resource::Resource const& resource, + config::otlp const& c) { + + auto const& http_opts = c.http_.value(); + auto opts = opentelemetry::exporter::otlp::OtlpHttpExporterOptions{}; + opts.url = http_opts.url_; + if (http_opts.content_type_ == "json") { + opts.content_type = + opentelemetry::exporter::otlp::HttpRequestContentType::kJson; + } else if (http_opts.content_type_ == "binary") { + opts.content_type = + opentelemetry::exporter::otlp::HttpRequestContentType::kBinary; + } else { + utl::fail("Invalid OTLP content type {}", http_opts.content_type_); + } + opts.use_json_name = http_opts.use_json_name_; + opts.timeout = std::chrono::seconds(c.timeout_); + for (auto [key, value] : c.headers_) { + opts.http_headers.insert({key, value}); + } + + auto exporter = + opentelemetry::exporter::otlp::OtlpHttpExporterFactory::Create(opts); + + auto processor = + opentelemetry::sdk::trace::SimpleSpanProcessorFactory::Create( + std::move(exporter)); + + auto sampler = opentelemetry::sdk::trace::AlwaysOnSamplerFactory::Create(); + + auto provider = + std::shared_ptr{opentelemetry::sdk::trace::TracerProviderFactory::Create( + std::move(processor), resource, std::move(sampler))}; + opentelemetry::trace::Provider::SetTracerProvider(provider); + provider_ = provider; +} + +void init_opentelemetry(config::otlp const& c, + std::string_view const motis_version) { + auto resource_attributes = opentelemetry::sdk::resource::ResourceAttributes{ + {"service.name", "motis"}, {"service.version", motis_version}}; + auto resource = + opentelemetry::sdk::resource::Resource::Create(resource_attributes); + + opentelemetry::context::RuntimeContext::SetRuntimeContextStorage( + std::make_shared()); + + if (c.http_.has_value()) { + init_opentelemetry_tracer(resource, c); + } + + opentelemetry::context::propagation::GlobalTextMapPropagator:: + SetGlobalPropagator( + std::make_shared< + opentelemetry::trace::propagation::HttpTraceContext>()); +} + +void cleanup_opentelemetry_tracer() { + if (provider_) { + provider_->ForceFlush(); + provider_.reset(); + } + + auto const none = std::shared_ptr(); + opentelemetry::trace::Provider::SetTracerProvider(none); +} + +} // namespace motis \ No newline at end of file diff --git a/tools/ubsan-suppress.txt b/tools/ubsan-suppress.txt index 98924a5a37..cf11b9113b 100644 --- a/tools/ubsan-suppress.txt +++ b/tools/ubsan-suppress.txt @@ -1,2 +1,3 @@ src:*/LuaJIT/* -src:*/libressl/* \ No newline at end of file +src:*/libressl/* +src:*/curl/* \ No newline at end of file