From b0cb2e1ab1555c8d44d42c1f5f8b0e3ac38efcb3 Mon Sep 17 00:00:00 2001 From: Vlad Paiu Date: Tue, 16 Dec 2025 10:50:44 +0200 Subject: [PATCH 1/8] Initial POC --- action.c | 31 ++ dprint.h | 2 +- modules/opentelemetry/Makefile | 47 +++ modules/opentelemetry/opentelemetry.cpp | 502 ++++++++++++++++++++++++ route_trace.c | 46 +++ route_trace.h | 87 ++++ 6 files changed, 714 insertions(+), 1 deletion(-) create mode 100644 modules/opentelemetry/Makefile create mode 100644 modules/opentelemetry/opentelemetry.cpp create mode 100644 route_trace.c create mode 100644 route_trace.h diff --git a/action.c b/action.c index a803d3b35b4..7556a645ab2 100644 --- a/action.c +++ b/action.c @@ -60,6 +60,7 @@ #include "script_var.h" #include "xlog.h" #include "cfg_pp.h" +#include "route_trace.h" #include @@ -251,9 +252,23 @@ int run_top_route(struct script_route sr, struct sip_msg* msg) else route_stack[route_stack_start] = sr.name; + if (route_trace_enabled()) { + route_trace_msg_start(msg, route_type, route_stack[route_stack_start], + route_stack_size, route_stack_start); + route_trace_route_enter(msg, route_type, route_stack[route_stack_start], + NULL, 0, route_stack_size, route_stack_start); + } + run_actions(sr.a, msg); ret = action_flags; + if (route_trace_enabled()) { + route_trace_route_exit(msg, route_type, route_stack[route_stack_start], + NULL, 0, route_stack_size, route_stack_start, ret); + route_trace_msg_end(msg, route_type, route_stack[route_stack_start], + route_stack_size, route_stack_start, ret); + } + if (route_stack_start_bkp != -1) { route_stack_size = route_stack_size_bkp; route_stack_start = route_stack_start_bkp; @@ -808,12 +823,28 @@ int do_action(struct action* a, struct sip_msg* msg) } route_params_push_level(sroutes->request[i].name, route_p, (void *)(unsigned long)len, route_param_get); + if (route_trace_enabled()) + route_trace_route_enter(msg, route_type, + sroutes->request[i].name, a->file, a->line, + route_stack_size, route_stack_start); return_code=run_actions(sroutes->request[i].a, msg); + if (route_trace_enabled()) + route_trace_route_exit(msg, route_type, + sroutes->request[i].name, a->file, a->line, + route_stack_size, route_stack_start, return_code); route_params_release(route_p, len); route_params_pop_level(); } else { route_params_push_level(sroutes->request[i].name, NULL, 0, route_param_get); + if (route_trace_enabled()) + route_trace_route_enter(msg, route_type, + sroutes->request[i].name, a->file, a->line, + route_stack_size, route_stack_start); return_code=run_actions(sroutes->request[i].a, msg); + if (route_trace_enabled()) + route_trace_route_exit(msg, route_type, + sroutes->request[i].name, a->file, a->line, + route_stack_size, route_stack_start, return_code); route_params_pop_level(); } ret=return_code; diff --git a/dprint.h b/dprint.h index ea5ab2cd794..c39471f1d24 100644 --- a/dprint.h +++ b/dprint.h @@ -105,7 +105,7 @@ #undef NO_LOG #endif -#define MAX_LOG_CONS_NO 3 +#define MAX_LOG_CONS_NO 4 #define STDERR_CONSUMER_NAME "stderror" #define SYSLOG_CONSUMER_NAME "syslog" diff --git a/modules/opentelemetry/Makefile b/modules/opentelemetry/Makefile new file mode 100644 index 00000000000..17ccb655210 --- /dev/null +++ b/modules/opentelemetry/Makefile @@ -0,0 +1,47 @@ +# WARNING: do not run this directly, it should be run by the master Makefile + +include ../../Makefile.defs + +NAME=opentelemetry.so + +CXX?=g++ + +# Make sure the C++ object participates in linking and overrides any defaults +# from the shared Makefile logic (which only considers .c sources). +override extra_objs+=opentelemetry.o + +# Set WITH_OTEL_CPP=1 to force using a locally provided SDK rooted at +# $(OTEL_CPP_ROOT). By default we try to pick up a system installation via +# pkg-config (opentelemetry_trace, _resources, _common, _api). +WITH_OTEL_CPP?=0 +OTEL_CPP_ROOT?=$(CURDIR)/../../../opentelemetry_cpplib +OTEL_CPP_CFLAGS?= +OTEL_CPP_LIBS?= + +ifeq ($(WITH_OTEL_CPP),0) +ifneq ($(CROSS_COMPILE),) +else +ifneq ($(shell pkg-config --exists opentelemetry_trace opentelemetry_resources opentelemetry_common opentelemetry_api && echo yes),) +WITH_OTEL_CPP:=1 +OTEL_CPP_CFLAGS+=$(shell pkg-config --cflags opentelemetry_trace opentelemetry_resources opentelemetry_common opentelemetry_api) +OTEL_CPP_LIBS+=$(shell pkg-config --libs opentelemetry_trace opentelemetry_resources opentelemetry_logs opentelemetry_metrics opentelemetry_common) +OTEL_CPP_LIBS+=-lopentelemetry_exporter_otlp_http +endif +endif +endif + +ifeq ($(WITH_OTEL_CPP),1) +DEFS+=-DHAVE_OPENTELEMETRY_CPP +CXXFLAGS+=-std=c++17 -fpermissive +ifeq ($(strip $(OTEL_CPP_CFLAGS)),) +OTEL_CPP_CFLAGS=-I$(OTEL_CPP_ROOT)/api/include -I$(OTEL_CPP_ROOT)/sdk/include \ + -I$(OTEL_CPP_ROOT)/exporters/include -I$(OTEL_CPP_ROOT)/resource_detectors/include +endif +CXXFLAGS+=$(OTEL_CPP_CFLAGS) +LIBS+=$(OTEL_CPP_LIBS) +endif + +include ../../Makefile.modules + +opentelemetry.o: opentelemetry.cpp + $(Q)$(CXX) $(CXXFLAGS) $(DEFS) $(INCLUDE) -fPIC -c $< -o $@ diff --git a/modules/opentelemetry/opentelemetry.cpp b/modules/opentelemetry/opentelemetry.cpp new file mode 100644 index 00000000000..7d982c77e8d --- /dev/null +++ b/modules/opentelemetry/opentelemetry.cpp @@ -0,0 +1,502 @@ +/* + * OpenTelemetry tracing for OpenSIPS routes + * + * This module wires the route_trace hooks and OpenSIPS log consumer into + * the OpenTelemetry C++ SDK. It keeps a per-process span stack and attaches + * worker logs as span events. + */ + +#include +#include +#include +#include +#include + +#ifdef HAVE_OPENTELEMETRY_CPP +#include "opentelemetry/trace/provider.h" +#include "opentelemetry/trace/scope.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/tracer.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/trace/batch_span_processor.h" +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/exporters/otlp/otlp_http_exporter_factory.h" +#include "opentelemetry/exporters/otlp/otlp_http_exporter_options.h" +namespace oteltrace = opentelemetry::trace; +namespace otelsdktrace = opentelemetry::sdk::trace; +namespace otelsdkresource = opentelemetry::sdk::resource; +namespace otelotlp = opentelemetry::exporter::otlp; +#endif + +#ifdef __cplusplus +/* Relax C-only headers for C++ compilation. */ +#define class class_keyword +#undef HAVE_STDATOMIC +#undef HAVE_GENERICS +#endif + +extern "C" { +#include "../../poll_types.h" +#include "../../sr_module.h" +#include "../../dprint.h" +#include "../../mem/mem.h" +#include "../../route_trace.h" +#include "../../log_interface.h" +#include "../../str.h" +#include "../../pt.h" +#include "../../version.h" +#include "../../ip_addr.h" +} + +#ifdef class +#undef class +#endif + +static int otel_enabled = 0; +static int otel_log_level = L_DBG; +static int otel_use_batch = 1; +static str otel_service_name = str_init("opensips"); +static str otel_exporter_endpoint = STR_NULL; + +struct otel_span { +#ifdef HAVE_OPENTELEMETRY_CPP + opentelemetry::nostd::shared_ptr span; + std::unique_ptr scope; +#endif + const char *name; + int route_type; + int depth; + int is_root; + const char *file; + int line; + struct otel_span *parent; +}; + +static __thread struct otel_span *otel_span_top; +static __thread int otel_log_in_cb; + +#ifdef HAVE_OPENTELEMETRY_CPP +static opentelemetry::nostd::shared_ptr otel_tracer; +static opentelemetry::nostd::shared_ptr otel_provider; +#endif + +static int mod_init(void); +static int child_init(int rank); +static void destroy(void); + +static inline const char *route_type_name(int route_type) +{ + switch (route_type) { + case REQUEST_ROUTE: return "request"; + case FAILURE_ROUTE: return "failure"; + case ONREPLY_ROUTE: return "onreply"; + case BRANCH_ROUTE: return "branch"; + case ERROR_ROUTE: return "error"; + case LOCAL_ROUTE: return "local"; + case STARTUP_ROUTE: return "startup"; + case TIMER_ROUTE: return "timer"; + case EVENT_ROUTE: return "event"; + default: return "unknown"; + } +} + +static void otel_span_reset(void) +{ + struct otel_span *span, *next; + + span = otel_span_top; + while (span) { + next = span->parent; +#ifdef HAVE_OPENTELEMETRY_CPP + if (span->span) + span->span->End(); + span->scope.reset(); +#endif + span->~otel_span(); + pkg_free(span); + span = next; + } + + otel_span_top = NULL; +} + +#ifdef HAVE_OPENTELEMETRY_CPP +static int otel_init_provider(void) +{ + std::string service_name(otel_service_name.s ? otel_service_name.s : "opensips", + otel_service_name.s ? otel_service_name.len : (int)strlen("opensips")); + + otelsdkresource::ResourceAttributes attrs = { + { "service.name", service_name }, + { "process.pid", (int64_t)my_pid() } + }; + + auto resource = otelsdkresource::Resource::Create(attrs); + + otelotlp::OtlpHttpExporterOptions opts; + if (otel_exporter_endpoint.len && otel_exporter_endpoint.s) + opts.url = std::string(otel_exporter_endpoint.s, otel_exporter_endpoint.len); + + auto exporter = otelotlp::OtlpHttpExporterFactory::Create(opts); + std::unique_ptr processor; + + if (otel_use_batch) { + otelsdktrace::BatchSpanProcessorOptions bs_opts; + processor = std::make_unique(std::move(exporter), bs_opts); + } else { + processor = std::make_unique(std::move(exporter)); + } + + auto provider = opentelemetry::nostd::shared_ptr( + std::make_shared(std::move(processor), resource)); + oteltrace::Provider::SetTracerProvider(provider); + otel_tracer = provider->GetTracer("opensips.opentelemetry", OPENSIPS_FULL_VERSION); + otel_provider = provider; + + return 0; +} + +static void otel_set_msg_attributes(struct sip_msg *msg, oteltrace::Span *span) +{ + if (!span || !msg) + return; + + if (msg->first_line.type == SIP_REQUEST) { + str *m = &msg->first_line.u.request.method; + span->SetAttribute("sip.method", opentelemetry::nostd::string_view(m->s, m->len)); + span->SetAttribute("opensips.top_route", route_type_name(REQUEST_ROUTE)); + if (msg->first_line.u.request.uri.s && msg->first_line.u.request.uri.len) + span->SetAttribute("sip.ruri", + opentelemetry::nostd::string_view(msg->first_line.u.request.uri.s, + msg->first_line.u.request.uri.len)); + } else if (msg->first_line.type == SIP_REPLY) { + span->SetAttribute("sip.status_code", (int64_t)msg->first_line.u.reply.statuscode); + if (msg->first_line.u.reply.reason.s && msg->first_line.u.reply.reason.len) + span->SetAttribute("sip.reason", + opentelemetry::nostd::string_view(msg->first_line.u.reply.reason.s, + msg->first_line.u.reply.reason.len)); + span->SetAttribute("opensips.top_route", route_type_name(ONREPLY_ROUTE)); + } + + if (msg->callid && msg->callid->body.s && msg->callid->body.len) + span->SetAttribute("sip.call_id", + opentelemetry::nostd::string_view(msg->callid->body.s, msg->callid->body.len)); + + if (msg->cseq && msg->cseq->body.s && msg->cseq->body.len) + span->SetAttribute("sip.cseq", + opentelemetry::nostd::string_view(msg->cseq->body.s, msg->cseq->body.len)); + + if (msg->via1 && msg->via1->host.s && msg->via1->host.len) + span->SetAttribute("net.host.ip", + opentelemetry::nostd::string_view(msg->via1->host.s, msg->via1->host.len)); + + span->SetAttribute("net.peer.ip", ip_addr2a(&msg->rcv.src_ip)); + span->SetAttribute("net.peer.port", (int64_t)msg->rcv.src_port); +} +#endif + +static struct otel_span *otel_span_start(const char *name, int route_type, + int depth, int is_root, const char *file, int line) +{ + struct otel_span *span; + + if (!otel_enabled) + return NULL; + + span = (struct otel_span *)pkg_malloc(sizeof *span); + if (!span) + return NULL; + new (span) otel_span(); + +#ifdef HAVE_OPENTELEMETRY_CPP + if (otel_tracer) { + oteltrace::StartSpanOptions opts; + opts.kind = oteltrace::SpanKind::kInternal; + if (otel_span_top && otel_span_top->span) + opts.parent = otel_span_top->span->GetContext(); + + auto s = otel_tracer->StartSpan(name ? name : "", opts); + s->SetAttribute("opensips.route.type", route_type_name(route_type)); + s->SetAttribute("opensips.route.depth", depth); + s->SetAttribute("opensips.route.is_root", (int64_t)is_root); + if (file) { + s->SetAttribute("code.filepath", file); + s->SetAttribute("code.lineno", line); + } + + span->scope = std::unique_ptr(new oteltrace::Scope(s)); + span->span = s; + } +#endif + + span->name = name; + span->route_type = route_type; + span->depth = depth; + span->is_root = is_root; + span->file = file; + span->line = line; + span->parent = otel_span_top; + + otel_span_top = span; + + return span; +} + +static void otel_span_end(struct otel_span *span) +{ + if (!span) + return; + +#ifdef HAVE_OPENTELEMETRY_CPP + if (span->span) + span->span->End(); + span->scope.reset(); + span->span = nullptr; +#endif + + otel_span_top = span->parent; + span->~otel_span(); + pkg_free(span); +} + +static void otel_on_msg_start(struct sip_msg *msg, int route_type, + const char *route_name, int stack_size, int stack_start) +{ + const char *name; + + if (!otel_enabled) + return; + + otel_span_reset(); + + name = route_name ? route_name : ""; + otel_span_start(name, route_type, stack_size - stack_start, 1, NULL, 0); + +#ifdef HAVE_OPENTELEMETRY_CPP + if (otel_span_top && otel_span_top->span) { + otel_set_msg_attributes(msg, otel_span_top->span.get()); + otel_span_top->span->SetAttribute("sip.raw", + opentelemetry::nostd::string_view(msg->buf, msg->len)); + } +#endif + + (void)msg; +} + +static void otel_on_msg_end(struct sip_msg *msg, int route_type, + const char *route_name, int stack_size, int stack_start, int status) +{ + if (!otel_enabled) + return; + + (void)msg; + (void)route_type; + (void)route_name; + (void)stack_size; + (void)stack_start; + (void)status; + + otel_span_reset(); +} + +static void otel_on_route_enter(struct sip_msg *msg, int route_type, + const char *route_name, const char *file, int line, + int stack_size, int stack_start) +{ + const char *name; + + if (!otel_enabled) + return; + + name = route_name ? route_name : ""; + otel_span_start(name, route_type, stack_size - stack_start, 0, file, line); + + (void)msg; +} + +static void otel_on_route_exit(struct sip_msg *msg, int route_type, + const char *route_name, const char *file, int line, + int stack_size, int stack_start, int status) +{ + if (!otel_enabled) + return; + +#ifdef HAVE_OPENTELEMETRY_CPP + if (otel_span_top && otel_span_top->span) + otel_span_top->span->SetAttribute("opensips.route.status", status); +#endif + + (void)msg; + (void)route_type; + (void)route_name; + (void)file; + (void)line; + (void)stack_size; + (void)stack_start; + + otel_span_end(otel_span_top); +} + +static const char *level_to_str(int level) +{ + switch (level) { + case L_ALERT: return "alert"; + case L_CRIT: return "crit"; + case L_ERR: return "error"; + case L_WARN: return "warn"; + case L_NOTICE: return "notice"; + case L_INFO: return "info"; + case L_DBG: return "debug"; + default: return "unknown"; + } +} + +static void otel_log_consumer(int level, int facility, const char *module, + const char *func, char *format, va_list ap) +{ + char buf[512]; + int len; + va_list ap_copy; + + if (!otel_enabled || otel_log_in_cb) + return; + + if (!otel_span_top) + return; + + otel_log_in_cb = 1; + + va_copy(ap_copy, ap); + len = vsnprintf(buf, sizeof(buf), format, ap_copy); + va_end(ap_copy); + + if (len < 0) { + otel_log_in_cb = 0; + return; + } + + if (len >= (int)sizeof(buf)) + len = sizeof(buf) - 1; + + buf[len] = '\0'; + +#ifdef HAVE_OPENTELEMETRY_CPP + if (otel_span_top->span) { + otel_span_top->span->AddEvent("log", { + { "log.level", level_to_str(level) }, + { "log.message", buf }, + { "code.function", func ? func : "" }, + { "opensips.module", module ? module : "" } + }); + } +#else + (void)level; + (void)facility; + (void)module; + (void)func; + (void)format; +#endif + + (void)facility; + + otel_log_in_cb = 0; +} + +static route_trace_handlers_t otel_trace_handlers = { + .on_msg_start = otel_on_msg_start, + .on_msg_end = otel_on_msg_end, + .on_route_enter = otel_on_route_enter, + .on_route_exit = otel_on_route_exit, +}; + +static const param_export_t params[] = { + { "enable", INT_PARAM, &otel_enabled }, + { "log_level", INT_PARAM, &otel_log_level }, + { "use_batch", INT_PARAM, &otel_use_batch }, + { "service_name", STR_PARAM, &otel_service_name.s }, + { "exporter_endpoint", STR_PARAM, &otel_exporter_endpoint.s }, + { 0, 0, 0 } +}; + +extern "C" struct module_exports exports = { + "opentelemetry", + MOD_TYPE_DEFAULT, + { OPENSIPS_FULL_VERSION, OPENSIPS_COMPILE_FLAGS, { VERSIONTYPE, THISREVISION } }, + DEFAULT_DLFLAGS, + 0, + 0, + 0, + 0, + params, + 0, + 0, + 0, + 0, + 0, + 0, + mod_init, + 0, + destroy, + child_init, + 0 +}; + +static int mod_init(void) +{ + if (!otel_enabled) { + LM_INFO("opentelemetry module disabled\n"); + return 0; + } + + if (otel_service_name.s && !otel_service_name.len) + otel_service_name.len = strlen(otel_service_name.s); + if (otel_exporter_endpoint.s && !otel_exporter_endpoint.len) + otel_exporter_endpoint.len = strlen(otel_exporter_endpoint.s); + +#ifdef HAVE_OPENTELEMETRY_CPP + if (register_route_tracer(&otel_trace_handlers) != 0) { + LM_ERR("failed to register route tracer hooks\n"); + return -1; + } + + if (register_log_consumer((char *)"opentelemetry", otel_log_consumer, + otel_log_level, 0) != 0) { + LM_ERR("failed to register OpenTelemetry log consumer\n"); + return -1; + } +#else + LM_ERR("OpenTelemetry C++ SDK not available - build with HAVE_OPENTELEMETRY_CPP\n"); + return -1; +#endif + + return 0; +} + +static int child_init(int rank) +{ + (void)rank; + + if (!otel_enabled) + return 0; + + otel_span_reset(); + otel_log_in_cb = 0; + +#ifdef HAVE_OPENTELEMETRY_CPP + if (otel_init_provider() != 0) { + LM_ERR("failed to initialize tracer provider\n"); + return -1; + } +#endif + + return 0; +} + +static void destroy(void) +{ + if (otel_enabled) + unregister_route_tracer(&otel_trace_handlers); + + otel_span_reset(); +} diff --git a/route_trace.c b/route_trace.c new file mode 100644 index 00000000000..104874b9d51 --- /dev/null +++ b/route_trace.c @@ -0,0 +1,46 @@ +/* + * Route tracing hooks for external instrumentation + * + * Copyright (C) 2024 + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "route_trace.h" +#include "dprint.h" + +route_trace_handlers_t *route_trace_handlers; + +int register_route_tracer(route_trace_handlers_t *handlers) +{ + if (!handlers) + return -1; + + if (route_trace_handlers && route_trace_handlers != handlers) { + LM_ERR("route tracer already registered\n"); + return -1; + } + + route_trace_handlers = handlers; + return 0; +} + +void unregister_route_tracer(route_trace_handlers_t *handlers) +{ + if (route_trace_handlers == handlers) + route_trace_handlers = NULL; +} diff --git a/route_trace.h b/route_trace.h new file mode 100644 index 00000000000..d450cc72de7 --- /dev/null +++ b/route_trace.h @@ -0,0 +1,87 @@ +/* + * Route tracing hooks for external instrumentation + * + * Copyright (C) 2024 + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ROUTE_TRACE_H +#define ROUTE_TRACE_H + +#include + +struct sip_msg; + +typedef struct route_trace_handlers { + void (*on_msg_start)(struct sip_msg *msg, int route_type, + const char *route_name, int stack_size, int stack_start); + void (*on_msg_end)(struct sip_msg *msg, int route_type, + const char *route_name, int stack_size, int stack_start, int status); + void (*on_route_enter)(struct sip_msg *msg, int route_type, + const char *route_name, const char *file, int line, + int stack_size, int stack_start); + void (*on_route_exit)(struct sip_msg *msg, int route_type, + const char *route_name, const char *file, int line, + int stack_size, int stack_start, int status); +} route_trace_handlers_t; + +extern route_trace_handlers_t *route_trace_handlers; + +int register_route_tracer(route_trace_handlers_t *handlers); +void unregister_route_tracer(route_trace_handlers_t *handlers); + +static inline int route_trace_enabled(void) +{ + return route_trace_handlers != NULL; +} + +static inline void route_trace_msg_start(struct sip_msg *msg, int route_type, + const char *route_name, int stack_size, int stack_start) +{ + if (route_trace_handlers && route_trace_handlers->on_msg_start) + route_trace_handlers->on_msg_start(msg, route_type, route_name, + stack_size, stack_start); +} + +static inline void route_trace_msg_end(struct sip_msg *msg, int route_type, + const char *route_name, int stack_size, int stack_start, int status) +{ + if (route_trace_handlers && route_trace_handlers->on_msg_end) + route_trace_handlers->on_msg_end(msg, route_type, route_name, + stack_size, stack_start, status); +} + +static inline void route_trace_route_enter(struct sip_msg *msg, int route_type, + const char *route_name, const char *file, int line, + int stack_size, int stack_start) +{ + if (route_trace_handlers && route_trace_handlers->on_route_enter) + route_trace_handlers->on_route_enter(msg, route_type, route_name, + file, line, stack_size, stack_start); +} + +static inline void route_trace_route_exit(struct sip_msg *msg, int route_type, + const char *route_name, const char *file, int line, + int stack_size, int stack_start, int status) +{ + if (route_trace_handlers && route_trace_handlers->on_route_exit) + route_trace_handlers->on_route_exit(msg, route_type, route_name, + file, line, stack_size, stack_start, status); +} + +#endif /* ROUTE_TRACE_H */ From 293ac085974b935832cecad557095f91a6192b22 Mon Sep 17 00:00:00 2001 From: Vlad Paiu Date: Tue, 16 Dec 2025 12:38:33 +0200 Subject: [PATCH 2/8] always use system lib --- modules/opentelemetry/Makefile | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/modules/opentelemetry/Makefile b/modules/opentelemetry/Makefile index 17ccb655210..76ab1e15676 100644 --- a/modules/opentelemetry/Makefile +++ b/modules/opentelemetry/Makefile @@ -10,36 +10,16 @@ CXX?=g++ # from the shared Makefile logic (which only considers .c sources). override extra_objs+=opentelemetry.o -# Set WITH_OTEL_CPP=1 to force using a locally provided SDK rooted at -# $(OTEL_CPP_ROOT). By default we try to pick up a system installation via -# pkg-config (opentelemetry_trace, _resources, _common, _api). -WITH_OTEL_CPP?=0 -OTEL_CPP_ROOT?=$(CURDIR)/../../../opentelemetry_cpplib -OTEL_CPP_CFLAGS?= -OTEL_CPP_LIBS?= - -ifeq ($(WITH_OTEL_CPP),0) -ifneq ($(CROSS_COMPILE),) -else -ifneq ($(shell pkg-config --exists opentelemetry_trace opentelemetry_resources opentelemetry_common opentelemetry_api && echo yes),) -WITH_OTEL_CPP:=1 -OTEL_CPP_CFLAGS+=$(shell pkg-config --cflags opentelemetry_trace opentelemetry_resources opentelemetry_common opentelemetry_api) -OTEL_CPP_LIBS+=$(shell pkg-config --libs opentelemetry_trace opentelemetry_resources opentelemetry_logs opentelemetry_metrics opentelemetry_common) -OTEL_CPP_LIBS+=-lopentelemetry_exporter_otlp_http -endif -endif +# Always use the system-installed OpenTelemetry C++ SDK (via pkg-config). +OTEL_CPP_CFLAGS:=$(shell pkg-config --cflags opentelemetry_trace opentelemetry_resources opentelemetry_common opentelemetry_api) +OTEL_CPP_LIBS:=$(shell pkg-config --libs opentelemetry_trace opentelemetry_resources opentelemetry_logs opentelemetry_metrics opentelemetry_common) -lopentelemetry_exporter_otlp_http +ifeq ($(strip $(OTEL_CPP_CFLAGS)$(OTEL_CPP_LIBS)),) +$(error OpenTelemetry C++ SDK not found via pkg-config (opentelemetry_*). Please install the system libraries.) endif -ifeq ($(WITH_OTEL_CPP),1) DEFS+=-DHAVE_OPENTELEMETRY_CPP -CXXFLAGS+=-std=c++17 -fpermissive -ifeq ($(strip $(OTEL_CPP_CFLAGS)),) -OTEL_CPP_CFLAGS=-I$(OTEL_CPP_ROOT)/api/include -I$(OTEL_CPP_ROOT)/sdk/include \ - -I$(OTEL_CPP_ROOT)/exporters/include -I$(OTEL_CPP_ROOT)/resource_detectors/include -endif -CXXFLAGS+=$(OTEL_CPP_CFLAGS) +CXXFLAGS+=-std=c++17 -fpermissive $(OTEL_CPP_CFLAGS) LIBS+=$(OTEL_CPP_LIBS) -endif include ../../Makefile.modules From 141b2ea9685c9e06cbc9e90776ebc2ac09661d77 Mon Sep 17 00:00:00 2001 From: Vlad Paiu Date: Thu, 8 Jan 2026 14:04:25 +0200 Subject: [PATCH 3/8] Add suport for async/resume jumps while maintaing spans --- action.c | 8 ++- modules/opentelemetry/opentelemetry.cpp | 84 +++++++++++++++++++++---- modules/tm/async.c | 28 ++++++++- route_trace.h | 26 ++++++++ 4 files changed, 132 insertions(+), 14 deletions(-) diff --git a/action.c b/action.c index 7556a645ab2..9a908612154 100644 --- a/action.c +++ b/action.c @@ -216,6 +216,8 @@ int run_top_route(struct script_route sr, struct sip_msg* msg) int bk_action_flags, route_stack_start_bkp = -1, route_stack_size_bkp; int ret; context_p ctx = NULL; + const char *trace_file = NULL; + int trace_line = 0; bk_action_flags = action_flags; @@ -253,10 +255,14 @@ int run_top_route(struct script_route sr, struct sip_msg* msg) route_stack[route_stack_start] = sr.name; if (route_trace_enabled()) { + if (sr.a && sr.a->file) { + trace_file = sr.a->file; + trace_line = sr.a->line; + } route_trace_msg_start(msg, route_type, route_stack[route_stack_start], route_stack_size, route_stack_start); route_trace_route_enter(msg, route_type, route_stack[route_stack_start], - NULL, 0, route_stack_size, route_stack_start); + trace_file, trace_line, route_stack_size, route_stack_start); } run_actions(sr.a, msg); diff --git a/modules/opentelemetry/opentelemetry.cpp b/modules/opentelemetry/opentelemetry.cpp index 7d982c77e8d..90f6187e7a0 100644 --- a/modules/opentelemetry/opentelemetry.cpp +++ b/modules/opentelemetry/opentelemetry.cpp @@ -8,6 +8,8 @@ #include #include +#include +#include #include #include #include @@ -16,6 +18,7 @@ #include "opentelemetry/trace/provider.h" #include "opentelemetry/trace/scope.h" #include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/tracer.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/batch_span_processor.h" @@ -76,6 +79,9 @@ struct otel_span { static __thread struct otel_span *otel_span_top; static __thread int otel_log_in_cb; +static __thread route_trace_ctx_t otel_parent_ctx; +static __thread int otel_parent_ctx_set; + #ifdef HAVE_OPENTELEMETRY_CPP static opentelemetry::nostd::shared_ptr otel_tracer; static opentelemetry::nostd::shared_ptr otel_provider; @@ -85,6 +91,12 @@ static int mod_init(void); static int child_init(int rank); static void destroy(void); +static void otel_parent_ctx_clear(void) +{ + memset(&otel_parent_ctx, 0, sizeof(otel_parent_ctx)); + otel_parent_ctx_set = 0; +} + static inline const char *route_type_name(int route_type) { switch (route_type) { @@ -101,6 +113,34 @@ static inline const char *route_type_name(int route_type) } } +static int otel_get_ctx(route_trace_ctx_t *ctx) +{ + if (!ctx) + return 0; + memset(ctx, 0, sizeof(*ctx)); +#ifdef HAVE_OPENTELEMETRY_CPP + if (otel_span_top && otel_span_top->span) { + auto sc = otel_span_top->span->GetContext(); + if (!sc.IsValid()) + return 0; + memcpy(ctx->trace_id, sc.trace_id().Id().data(), sizeof(ctx->trace_id)); + memcpy(ctx->span_id, sc.span_id().Id().data(), sizeof(ctx->span_id)); + ctx->trace_flags = sc.trace_flags().flags(); + return 1; + } +#endif + return 0; +} + +static int otel_set_ctx(const route_trace_ctx_t *ctx) +{ + if (!ctx) + return 0; + memcpy(&otel_parent_ctx, ctx, sizeof(otel_parent_ctx)); + otel_parent_ctx_set = 1; + return 1; +} + static void otel_span_reset(void) { struct otel_span *span, *next; @@ -200,6 +240,8 @@ static struct otel_span *otel_span_start(const char *name, int route_type, int depth, int is_root, const char *file, int line) { struct otel_span *span; + int has_parent_ctx = 0; + int has_parent_link = 0; if (!otel_enabled) return NULL; @@ -213,13 +255,32 @@ static struct otel_span *otel_span_start(const char *name, int route_type, if (otel_tracer) { oteltrace::StartSpanOptions opts; opts.kind = oteltrace::SpanKind::kInternal; - if (otel_span_top && otel_span_top->span) + if (otel_span_top && otel_span_top->span) { opts.parent = otel_span_top->span->GetContext(); + has_parent_ctx = 1; + has_parent_link = 1; + } else if (otel_parent_ctx_set) { + has_parent_ctx = 1; + opentelemetry::trace::TraceId tid(opentelemetry::nostd::span(otel_parent_ctx.trace_id, 16)); + opentelemetry::trace::SpanId sid(opentelemetry::nostd::span(otel_parent_ctx.span_id, 8)); + opentelemetry::trace::TraceFlags tf(otel_parent_ctx.trace_flags); + opentelemetry::trace::SpanContext sc(tid, sid, tf, true); + if (sc.IsValid()) { + opts.parent = sc; + has_parent_link = 1; + if (otel_parent_ctx.has_start_time) { + opts.start_system_time = opentelemetry::common::SystemTimestamp( + std::chrono::nanoseconds(otel_parent_ctx.start_system_ns)); + opts.start_steady_time = opentelemetry::common::SteadyTimestamp( + std::chrono::nanoseconds(otel_parent_ctx.start_steady_ns)); + } + } + otel_parent_ctx_clear(); + } auto s = otel_tracer->StartSpan(name ? name : "", opts); s->SetAttribute("opensips.route.type", route_type_name(route_type)); - s->SetAttribute("opensips.route.depth", depth); - s->SetAttribute("opensips.route.is_root", (int64_t)is_root); + s->SetAttribute("opensips.route.is_root", (int64_t)(has_parent_link ? 0 : is_root)); if (file) { s->SetAttribute("code.filepath", file); s->SetAttribute("code.lineno", line); @@ -268,9 +329,12 @@ static void otel_on_msg_start(struct sip_msg *msg, int route_type, if (!otel_enabled) return; + if (otel_parent_ctx_set) + return; + otel_span_reset(); - name = route_name ? route_name : ""; + name = ""; otel_span_start(name, route_type, stack_size - stack_start, 1, NULL, 0); #ifdef HAVE_OPENTELEMETRY_CPP @@ -322,11 +386,6 @@ static void otel_on_route_exit(struct sip_msg *msg, int route_type, if (!otel_enabled) return; -#ifdef HAVE_OPENTELEMETRY_CPP - if (otel_span_top && otel_span_top->span) - otel_span_top->span->SetAttribute("opensips.route.status", status); -#endif - (void)msg; (void)route_type; (void)route_name; @@ -385,9 +444,7 @@ static void otel_log_consumer(int level, int facility, const char *module, if (otel_span_top->span) { otel_span_top->span->AddEvent("log", { { "log.level", level_to_str(level) }, - { "log.message", buf }, - { "code.function", func ? func : "" }, - { "opensips.module", module ? module : "" } + { "log.message", buf } }); } #else @@ -408,6 +465,8 @@ static route_trace_handlers_t otel_trace_handlers = { .on_msg_end = otel_on_msg_end, .on_route_enter = otel_on_route_enter, .on_route_exit = otel_on_route_exit, + .get_ctx = otel_get_ctx, + .set_ctx = otel_set_ctx, }; static const param_export_t params[] = { @@ -482,6 +541,7 @@ static int child_init(int rank) otel_span_reset(); otel_log_in_cb = 0; + otel_parent_ctx_clear(); #ifdef HAVE_OPENTELEMETRY_CPP if (otel_init_provider() != 0) { diff --git a/modules/tm/async.c b/modules/tm/async.c index 60354e7cbe2..0f6600c40a1 100644 --- a/modules/tm/async.c +++ b/modules/tm/async.c @@ -25,8 +25,11 @@ #include "../../dprint.h" #include "../../async.h" +#include "../../action.h" #include "../../context.h" #include "../../reactor_defs.h" +#include "../../route_trace.h" +#include #include "h_table.h" #include "t_lookup.h" #include "t_msgbuilder.h" @@ -36,6 +39,8 @@ typedef struct _async_tm_ctx { /* generic async context - MUST BE FIRST */ async_ctx async; + route_trace_ctx_t parent_ctx; + int parent_ctx_set; /* the script route to be used to continue after the resume function; * this is a reference in shm mem, that needs separated free */ struct script_route_ref *resume_route; @@ -93,6 +98,7 @@ int t_resume_async_request(int fd, void*param, int was_timeout) else LM_DBG("resuming request without a fd, transaction %p \n", t); + /* prepare for resume route, by filling in a phony UAC structure to * trigger the inheritance of the branch specific values */ uac.br_flags = getb0flags( t->uas.request ) ; @@ -186,6 +192,8 @@ int t_resume_async_request(int fd, void*param, int was_timeout) ctx->resume_route->name.s); } else { swap_route_type(route, ctx->route_type); + if (ctx->parent_ctx_set) + route_trace_set_ctx(&ctx->parent_ctx); run_resume_route( ctx->resume_route, &faked_req, 1); set_route_type(route); } @@ -237,6 +245,7 @@ int t_resume_async_reply(int fd, void*param, int was_timeout) LM_DBG("resuming reply without a fd, transaction %p \n", t); + /* enviroment setting */ current_processing_ctx = ctx->msg_ctx; backup_t = get_t(); @@ -332,6 +341,8 @@ int t_resume_async_reply(int fd, void*param, int was_timeout) ctx->resume_route->name.s); } else { swap_route_type(route, ctx->route_type); + if (ctx->parent_ctx_set) + route_trace_set_ctx(&ctx->parent_ctx); /* do not run any post script callback, we are a reply */ run_resume_route( ctx->resume_route, ctx->reply, 0); set_route_type(route); @@ -452,6 +463,22 @@ int t_handle_async(struct sip_msg *msg, struct action* a, memset(ctx,0,sizeof(async_tm_ctx)); ctx->async.timeout_s = timeout; + ctx->parent_ctx_set = route_trace_get_ctx(&ctx->parent_ctx); + if (ctx->parent_ctx_set) { + struct timespec ts; + int have_sys = 0; + int have_steady = 0; + if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { + ctx->parent_ctx.start_system_ns = ((uint64_t)ts.tv_sec * 1000000000ULL) + (uint64_t)ts.tv_nsec; + have_sys = 1; + } + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { + ctx->parent_ctx.start_steady_ns = ((uint64_t)ts.tv_sec * 1000000000ULL) + (uint64_t)ts.tv_nsec; + have_steady = 1; + } + if (have_sys && have_steady) + ctx->parent_ctx.has_start_time = 1; + } async_status = ASYNC_NO_IO; /*assume default status "no IO done" */ return_code = ((const acmd_export_t*)(a->elem[0].u.data_const))->function(msg, @@ -608,4 +635,3 @@ int t_handle_async(struct sip_msg *msg, struct action* a, /* the triggering route is terminated and whole script ended */ return 0; } - diff --git a/route_trace.h b/route_trace.h index d450cc72de7..0f88932f1bf 100644 --- a/route_trace.h +++ b/route_trace.h @@ -24,9 +24,19 @@ #define ROUTE_TRACE_H #include +#include struct sip_msg; +typedef struct route_trace_ctx { + uint8_t trace_id[16]; + uint8_t span_id[8]; + uint8_t trace_flags; + uint64_t start_system_ns; + uint64_t start_steady_ns; + uint8_t has_start_time; +} route_trace_ctx_t; + typedef struct route_trace_handlers { void (*on_msg_start)(struct sip_msg *msg, int route_type, const char *route_name, int stack_size, int stack_start); @@ -38,6 +48,8 @@ typedef struct route_trace_handlers { void (*on_route_exit)(struct sip_msg *msg, int route_type, const char *route_name, const char *file, int line, int stack_size, int stack_start, int status); + int (*get_ctx)(route_trace_ctx_t *ctx); + int (*set_ctx)(const route_trace_ctx_t *ctx); } route_trace_handlers_t; extern route_trace_handlers_t *route_trace_handlers; @@ -84,4 +96,18 @@ static inline void route_trace_route_exit(struct sip_msg *msg, int route_type, file, line, stack_size, stack_start, status); } +static inline int route_trace_get_ctx(route_trace_ctx_t *ctx) +{ + if (route_trace_handlers && route_trace_handlers->get_ctx) + return route_trace_handlers->get_ctx(ctx); + return 0; +} + +static inline int route_trace_set_ctx(const route_trace_ctx_t *ctx) +{ + if (route_trace_handlers && route_trace_handlers->set_ctx) + return route_trace_handlers->set_ctx(ctx); + return 0; +} + #endif /* ROUTE_TRACE_H */ From 29e80a864294162dc568b99b7fc66d044af803d8 Mon Sep 17 00:00:00 2001 From: Vlad Paiu Date: Thu, 8 Jan 2026 14:37:11 +0200 Subject: [PATCH 4/8] Add otel_enable MI command to enable/disable opentelemetry tracing --- modules/opentelemetry/opentelemetry.cpp | 193 ++++++++++++++++++++---- 1 file changed, 164 insertions(+), 29 deletions(-) diff --git a/modules/opentelemetry/opentelemetry.cpp b/modules/opentelemetry/opentelemetry.cpp index 90f6187e7a0..1f52c9b9e0e 100644 --- a/modules/opentelemetry/opentelemetry.cpp +++ b/modules/opentelemetry/opentelemetry.cpp @@ -44,19 +44,22 @@ extern "C" { #include "../../sr_module.h" #include "../../dprint.h" #include "../../mem/mem.h" +#include "../../mem/shm_mem.h" #include "../../route_trace.h" #include "../../log_interface.h" #include "../../str.h" #include "../../pt.h" #include "../../version.h" #include "../../ip_addr.h" +#include "../../mi/mi.h" } #ifdef class #undef class #endif -static int otel_enabled = 0; +static int otel_enabled_cfg = 0; +static int *otel_enabled = NULL; static int otel_log_level = L_DBG; static int otel_use_batch = 1; static str otel_service_name = str_init("opensips"); @@ -81,6 +84,8 @@ static __thread int otel_log_in_cb; static __thread route_trace_ctx_t otel_parent_ctx; static __thread int otel_parent_ctx_set; +static int otel_trace_registered; +static int otel_log_consumer_registered; #ifdef HAVE_OPENTELEMETRY_CPP static opentelemetry::nostd::shared_ptr otel_tracer; @@ -90,6 +95,21 @@ static opentelemetry::nostd::shared_ptr otel_provider static int mod_init(void); static int child_init(int rank); static void destroy(void); +static mi_response_t *otel_mi_enable(const mi_params_t *params, + struct mi_handler *async_hdl); +static void otel_log_consumer(int level, int facility, const char *module, + const char *func, char *format, va_list ap); +static int otel_ensure_provider(void); +extern route_trace_handlers_t otel_trace_handlers; +#ifdef HAVE_OPENTELEMETRY_CPP +static int otel_init_provider(void); +#endif +static void otel_span_reset(void); + +static inline int otel_is_enabled(void) +{ + return otel_enabled && *otel_enabled; +} static void otel_parent_ctx_clear(void) { @@ -97,6 +117,50 @@ static void otel_parent_ctx_clear(void) otel_parent_ctx_set = 0; } +static int otel_ensure_provider(void) +{ +#ifdef HAVE_OPENTELEMETRY_CPP + if (otel_is_enabled() && !otel_tracer) { + if (otel_init_provider() != 0) { + LM_ERR("failed to initialize tracer provider\n"); + return -1; + } + } +#endif + return 0; +} + +static int otel_runtime_set_enable(int enable) +{ + if (enable) { + if (otel_is_enabled()) + return 0; + if (otel_enabled) + *otel_enabled = 1; + otel_enabled_cfg = 1; + + if (otel_ensure_provider() != 0) { + if (otel_enabled) + *otel_enabled = 0; + otel_enabled_cfg = 0; + return -1; + } + return 0; + } + + if (!otel_is_enabled()) + return 0; + + if (otel_enabled) + *otel_enabled = 0; + otel_enabled_cfg = 0; + + otel_span_reset(); + otel_parent_ctx_clear(); + + return 0; +} + static inline const char *route_type_name(int route_type) { switch (route_type) { @@ -243,7 +307,10 @@ static struct otel_span *otel_span_start(const char *name, int route_type, int has_parent_ctx = 0; int has_parent_link = 0; - if (!otel_enabled) + if (!otel_is_enabled()) + return NULL; + + if (otel_ensure_provider() != 0) return NULL; span = (struct otel_span *)pkg_malloc(sizeof *span); @@ -326,7 +393,10 @@ static void otel_on_msg_start(struct sip_msg *msg, int route_type, { const char *name; - if (!otel_enabled) + if (!otel_is_enabled()) + return; + + if (otel_ensure_provider() != 0) return; if (otel_parent_ctx_set) @@ -351,7 +421,10 @@ static void otel_on_msg_start(struct sip_msg *msg, int route_type, static void otel_on_msg_end(struct sip_msg *msg, int route_type, const char *route_name, int stack_size, int stack_start, int status) { - if (!otel_enabled) + if (!otel_is_enabled()) + return; + + if (otel_ensure_provider() != 0) return; (void)msg; @@ -370,7 +443,10 @@ static void otel_on_route_enter(struct sip_msg *msg, int route_type, { const char *name; - if (!otel_enabled) + if (!otel_is_enabled()) + return; + + if (otel_ensure_provider() != 0) return; name = route_name ? route_name : ""; @@ -383,7 +459,10 @@ static void otel_on_route_exit(struct sip_msg *msg, int route_type, const char *route_name, const char *file, int line, int stack_size, int stack_start, int status) { - if (!otel_enabled) + if (!otel_is_enabled()) + return; + + if (otel_ensure_provider() != 0) return; (void)msg; @@ -418,7 +497,10 @@ static void otel_log_consumer(int level, int facility, const char *module, int len; va_list ap_copy; - if (!otel_enabled || otel_log_in_cb) + if (!otel_is_enabled() || otel_log_in_cb) + return; + + if (otel_ensure_provider() != 0) return; if (!otel_span_top) @@ -460,7 +542,7 @@ static void otel_log_consumer(int level, int facility, const char *module, otel_log_in_cb = 0; } -static route_trace_handlers_t otel_trace_handlers = { +route_trace_handlers_t otel_trace_handlers = { .on_msg_start = otel_on_msg_start, .on_msg_end = otel_on_msg_end, .on_route_enter = otel_on_route_enter, @@ -470,7 +552,7 @@ static route_trace_handlers_t otel_trace_handlers = { }; static const param_export_t params[] = { - { "enable", INT_PARAM, &otel_enabled }, + { "enable", INT_PARAM, &otel_enabled_cfg }, { "log_level", INT_PARAM, &otel_log_level }, { "use_batch", INT_PARAM, &otel_use_batch }, { "service_name", STR_PARAM, &otel_service_name.s }, @@ -478,6 +560,15 @@ static const param_export_t params[] = { { 0, 0, 0 } }; +static const mi_export_t mi_cmds[] = { + { "otel_enable", 0, 0, 0, { + { otel_mi_enable, { "enable", 0 } }, + { EMPTY_MI_RECIPE } + } + }, + { EMPTY_MI_EXPORT } +}; + extern "C" struct module_exports exports = { "opentelemetry", MOD_TYPE_DEFAULT, @@ -489,7 +580,7 @@ extern "C" struct module_exports exports = { 0, params, 0, - 0, + mi_cmds, 0, 0, 0, @@ -504,8 +595,16 @@ extern "C" struct module_exports exports = { static int mod_init(void) { if (!otel_enabled) { + otel_enabled = (int *)shm_malloc(sizeof(*otel_enabled)); + if (!otel_enabled) { + LM_ERR("no shm memory for opentelemetry enable\n"); + return -1; + } + *otel_enabled = otel_enabled_cfg; + } + + if (!otel_is_enabled()) { LM_INFO("opentelemetry module disabled\n"); - return 0; } if (otel_service_name.s && !otel_service_name.len) @@ -514,19 +613,12 @@ static int mod_init(void) otel_exporter_endpoint.len = strlen(otel_exporter_endpoint.s); #ifdef HAVE_OPENTELEMETRY_CPP - if (register_route_tracer(&otel_trace_handlers) != 0) { - LM_ERR("failed to register route tracer hooks\n"); - return -1; - } - - if (register_log_consumer((char *)"opentelemetry", otel_log_consumer, - otel_log_level, 0) != 0) { - LM_ERR("failed to register OpenTelemetry log consumer\n"); + /* no provider init here; each process initializes on demand */ +#else + if (otel_is_enabled()) { + LM_ERR("OpenTelemetry C++ SDK not available - build with HAVE_OPENTELEMETRY_CPP\n"); return -1; } -#else - LM_ERR("OpenTelemetry C++ SDK not available - build with HAVE_OPENTELEMETRY_CPP\n"); - return -1; #endif return 0; @@ -536,26 +628,69 @@ static int child_init(int rank) { (void)rank; - if (!otel_enabled) - return 0; - otel_span_reset(); otel_log_in_cb = 0; otel_parent_ctx_clear(); #ifdef HAVE_OPENTELEMETRY_CPP - if (otel_init_provider() != 0) { - LM_ERR("failed to initialize tracer provider\n"); - return -1; + if (!otel_trace_registered) { + if (register_route_tracer(&otel_trace_handlers) != 0) { + LM_ERR("failed to register route tracer hooks\n"); + return -1; + } + otel_trace_registered = 1; } + + if (!otel_log_consumer_registered) { + if (register_log_consumer((char *)"opentelemetry", otel_log_consumer, + otel_log_level, 0) != 0) { + LM_ERR("failed to register OpenTelemetry log consumer\n"); + return -1; + } + otel_log_consumer_registered = 1; + } + + if (otel_ensure_provider() != 0) + return -1; #endif return 0; } +static mi_response_t *otel_mi_enable(const mi_params_t *params, + struct mi_handler *async_hdl) +{ + mi_response_t *resp; + mi_item_t *resp_obj; + int enable; + int rc; + + (void)async_hdl; + + if (get_mi_int_param(params, "enable", &enable) < 0) + return init_mi_param_error(); + + if (enable != 0 && enable != 1) + return init_mi_error(400, MI_SSTR("Bad enable value")); + + rc = otel_runtime_set_enable(enable); + if (rc < 0) + return init_mi_error(500, MI_SSTR("Failed to update enable")); + + resp = init_mi_result_object(&resp_obj); + if (!resp) + return 0; + if (add_mi_number(resp_obj, MI_SSTR("enabled"), enable) < 0) { + free_mi_response(resp); + return 0; + } + + return resp; +} + static void destroy(void) { - if (otel_enabled) + if (otel_is_enabled()) unregister_route_tracer(&otel_trace_handlers); otel_span_reset(); From 4ec872e966555b799990af82da5cb34b6cf40f0b Mon Sep 17 00:00:00 2001 From: Vlad Paiu Date: Thu, 8 Jan 2026 15:04:15 +0200 Subject: [PATCH 5/8] Fix copyrights --- dprint.h | 1 + modules/opentelemetry/opentelemetry.cpp | 25 +++++++++++++++++++++---- route_trace.c | 9 ++++----- route_trace.h | 9 ++++----- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/dprint.h b/dprint.h index c39471f1d24..2fed36585e9 100644 --- a/dprint.h +++ b/dprint.h @@ -110,6 +110,7 @@ #define STDERR_CONSUMER_NAME "stderror" #define SYSLOG_CONSUMER_NAME "syslog" #define EVENT_CONSUMER_NAME "event" +#define OTEL_CONSUMER_NAME "opentelemetry" #define LOG_PLAIN_NAME "plain_text" #define LOG_JSON_NAME "json" diff --git a/modules/opentelemetry/opentelemetry.cpp b/modules/opentelemetry/opentelemetry.cpp index 1f52c9b9e0e..cd0c8b52728 100644 --- a/modules/opentelemetry/opentelemetry.cpp +++ b/modules/opentelemetry/opentelemetry.cpp @@ -1,11 +1,28 @@ /* * OpenTelemetry tracing for OpenSIPS routes * - * This module wires the route_trace hooks and OpenSIPS log consumer into - * the OpenTelemetry C++ SDK. It keeps a per-process span stack and attaches - * worker logs as span events. + * Copyright (C) 2026 OpenSIPS Project + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * History: + * -------- + * 2026-01-05 initial release (vlad) */ + #include #include #include @@ -642,7 +659,7 @@ static int child_init(int rank) } if (!otel_log_consumer_registered) { - if (register_log_consumer((char *)"opentelemetry", otel_log_consumer, + if (register_log_consumer(OTEL_CONSUMER_NAME, otel_log_consumer, otel_log_level, 0) != 0) { LM_ERR("failed to register OpenTelemetry log consumer\n"); return -1; diff --git a/route_trace.c b/route_trace.c index 104874b9d51..f8e69096bac 100644 --- a/route_trace.c +++ b/route_trace.c @@ -1,14 +1,12 @@ /* * Route tracing hooks for external instrumentation * - * Copyright (C) 2024 - * - * This file is part of opensips, a free SIP server. + * Copyright (C) 2026 OpenSIPS Project * * opensips is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version + * (at your option) any later version. * * opensips is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -17,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * */ #include "route_trace.h" diff --git a/route_trace.h b/route_trace.h index 0f88932f1bf..5b4dbd50e49 100644 --- a/route_trace.h +++ b/route_trace.h @@ -1,14 +1,12 @@ /* * Route tracing hooks for external instrumentation * - * Copyright (C) 2024 - * - * This file is part of opensips, a free SIP server. + * Copyright (C) 2026 OpenSIPS Project * * opensips is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version + * (at your option) any later version. * * opensips is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -17,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * */ #ifndef ROUTE_TRACE_H From b3e1e78b133a119ecebf9dff2a9c906005ab81f2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 9 Jan 2026 14:22:09 +0200 Subject: [PATCH 6/8] Add opentelemetry documentation --- modules/opentelemetry/doc/opentelemetry.xml | 27 +++ .../opentelemetry/doc/opentelemetry_admin.xml | 203 ++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 modules/opentelemetry/doc/opentelemetry.xml create mode 100644 modules/opentelemetry/doc/opentelemetry_admin.xml diff --git a/modules/opentelemetry/doc/opentelemetry.xml b/modules/opentelemetry/doc/opentelemetry.xml new file mode 100644 index 00000000000..a5e579e9dff --- /dev/null +++ b/modules/opentelemetry/doc/opentelemetry.xml @@ -0,0 +1,27 @@ + + + + + + +%docentities; + +]> + + + + opentelemetry Module + &osipsname; + + + + &admin; + &contrib; + + &docCopyrights; + ©right; 2026 &osipsproj; + + diff --git a/modules/opentelemetry/doc/opentelemetry_admin.xml b/modules/opentelemetry/doc/opentelemetry_admin.xml new file mode 100644 index 00000000000..984db27ae2c --- /dev/null +++ b/modules/opentelemetry/doc/opentelemetry_admin.xml @@ -0,0 +1,203 @@ + + + + + &adminguide; + +
+ Overview + + The opentelemetry module provides OpenTelemetry + tracing for &osips; route execution. It creates a root span per + processed SIP message and a child span for each route entry. + + + Spans include common SIP attributes (method, Call-ID, CSeq, status) and + message metadata. While a span is active, &osips; logs can be attached + as OpenTelemetry events for easier correlation. + + + Trace data is exported via the OTLP/HTTP exporter from the + OpenTelemetry C++ SDK. + +
+ +
+ Dependencies +
+ &osips; Modules + + The following modules must be loaded before this module: + + + + None. + + + + +
+ +
+ External Libraries or Applications + + The following libraries or applications must be installed before + running &osips; with this module loaded: + + + + OpenTelemetry C++ SDK (opentelemetry-cpp), + with the OTLP/HTTP exporter enabled. + + + + +
+
+ +
+ Exported Parameters + +
+ <varname>enable</varname> (integer) + + Enables or disables OpenTelemetry tracing at startup. It can also be + changed at runtime using the otel_enable + MI command. + + + If &osips; was built without the OpenTelemetry C++ SDK, enabling this + parameter will fail at startup. + + + + Default value is 0 (disabled). + + + + Set <varname>enable</varname> parameter + +... +modparam("opentelemetry", "enable", 1) +... + + +
+ +
+ <varname>log_level</varname> (integer) + + Log level threshold used by the OpenTelemetry log consumer when + attaching log events to the active span. + + + + Default value is L_DBG. + + + + Set <varname>log_level</varname> parameter + +... +modparam("opentelemetry", "log_level", 3) +... + + +
+ +
+ <varname>use_batch</varname> (integer) + + Selects the OpenTelemetry span processor. When enabled, the module uses + the batch span processor; otherwise it uses the simple span processor. + + + + Default value is 1 (enabled). + + + + Set <varname>use_batch</varname> parameter + +... +modparam("opentelemetry", "use_batch", 0) +... + + +
+ +
+ <varname>service_name</varname> (string) + + Sets the OpenTelemetry service.name resource attribute. + + + + Default value is opensips. + + + + Set <varname>service_name</varname> parameter + +... +modparam("opentelemetry", "service_name", "edge-proxy") +... + + +
+ +
+ <varname>exporter_endpoint</varname> (string) + + Overrides the OTLP/HTTP exporter endpoint. If empty, the OpenTelemetry + SDK default is used. + + + + Default value is empty. + + + + Set <varname>exporter_endpoint</varname> parameter + +... +modparam("opentelemetry", "exporter_endpoint", "http://127.0.0.1:4318/v1/traces") +... + + +
+
+ +
+ Exported MI Functions + +
+ + <function moreinfo="none">otel_enable</function> + + + Enables or disables OpenTelemetry tracing at runtime. + + + Name: otel_enable + + Parameters: + + + enable - set to 1 to enable + tracing or 0 to disable it. + + + + MI FIFO Command Format: + + + ## enable tracing + opensips-cli -x mi otel_enable enable=1 + ## disable tracing + opensips-cli -x mi otel_enable enable=0 + +
+
+ +
From 13937452efeedfc7deb6021465d0e6c0fd8b18e4 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 9 Jan 2026 14:25:00 +0200 Subject: [PATCH 7/8] Add opentelemetry to list of excluded modules from default compilation --- Makefile.conf.template | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile.conf.template b/Makefile.conf.template index b4581331198..7edbde4ab4d 100644 --- a/Makefile.conf.template +++ b/Makefile.conf.template @@ -38,6 +38,7 @@ #mi_xmlrpc_ng= New version of the xmlrpc server that handles xmlrpc requests and generates xmlrpc responses. | parsing/building XML library, typically libxml #mmgeoip= Lightweight wrapper for the MaxMind GeoIP API | libGeoIP #osp= Enables OpenSIPS to support secure, multi-lateral peering using the OSP standard | OSP development kit, typically osptoolkit +#opentelemetry= OpenTelemetry tracing for OpenSIPS routes | OpenTelemetry C++ SDK (opentelemetry-cpp) #perl= Easily implement your own OpenSIPS extensions in Perl | Perl library development files, typically libperl-dev #pi_http= Provides a simple web database provisioning interface | XML parsing & building library, typically libxml-dev #rabbitmq_consumer= Receive AMQP messages which will be delivered by triggering events | RabbitMQ development library, librabbitmq-dev @@ -75,7 +76,7 @@ #uuid= UUID generator | uuid-dev # the below definition must be one single line (no wrapping) to make the "menuconfig" tool happy -exclude_modules?= aaa_diameter aaa_radius auth_jwt auth_web3 b2b_logic_xml cachedb_cassandra cachedb_couchbase cachedb_dynamodb cachedb_memcached cachedb_mongodb cachedb_redis carrierroute cgrates compression cpl_c db_berkeley db_http db_mysql db_oracle db_perlvdb db_postgres db_sqlite db_unixodbc dialplan emergency event_rabbitmq event_kafka event_sqs h350 httpd http2d identity jabber json launch_darkly ldap lua mi_xmlrpc_ng mmgeoip osp perl pi_http presence presence_dialoginfo presence_mwi presence_reginfo presence_xml presence_dfks proto_ipsec proto_sctp proto_tls proto_wss pua pua_bla pua_dialoginfo pua_mi pua_reginfo pua_usrloc pua_xmpp python regex rabbitmq_consumer rest_client rls rtp.io siprec sngtc snmpstats stir_shaken tls_mgm tls_openssl tls_wolfssl uuid xcap xcap_client xml xmpp +exclude_modules?= aaa_diameter aaa_radius auth_jwt auth_web3 b2b_logic_xml cachedb_cassandra cachedb_couchbase cachedb_dynamodb cachedb_memcached cachedb_mongodb cachedb_redis carrierroute cgrates compression cpl_c db_berkeley db_http db_mysql db_oracle db_perlvdb db_postgres db_sqlite db_unixodbc dialplan emergency event_rabbitmq event_kafka event_sqs h350 httpd http2d identity jabber json launch_darkly ldap lua mi_xmlrpc_ng mmgeoip opentelemetry osp perl pi_http presence presence_dialoginfo presence_mwi presence_reginfo presence_xml presence_dfks proto_ipsec proto_sctp proto_tls proto_wss pua pua_bla pua_dialoginfo pua_mi pua_reginfo pua_usrloc pua_xmpp python regex rabbitmq_consumer rest_client rls rtp.io siprec sngtc snmpstats stir_shaken tls_mgm tls_openssl tls_wolfssl uuid xcap xcap_client xml xmpp include_modules?= From 468c862359e9b047c2aa6d60bb8369f6690c1b2b Mon Sep 17 00:00:00 2001 From: Vlad Paiu Date: Tue, 13 Jan 2026 14:17:29 +0200 Subject: [PATCH 8/8] Fixed lengths for service_name and exporter_endpoint params --- modules/opentelemetry/opentelemetry.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/opentelemetry/opentelemetry.cpp b/modules/opentelemetry/opentelemetry.cpp index cd0c8b52728..1231ab6eb5d 100644 --- a/modules/opentelemetry/opentelemetry.cpp +++ b/modules/opentelemetry/opentelemetry.cpp @@ -624,9 +624,9 @@ static int mod_init(void) LM_INFO("opentelemetry module disabled\n"); } - if (otel_service_name.s && !otel_service_name.len) + if (otel_service_name.s) otel_service_name.len = strlen(otel_service_name.s); - if (otel_exporter_endpoint.s && !otel_exporter_endpoint.len) + if (otel_exporter_endpoint.s) otel_exporter_endpoint.len = strlen(otel_exporter_endpoint.s); #ifdef HAVE_OPENTELEMETRY_CPP