You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2022/04/20 22:43:08 UTC
[trafficserver] branch 9.2.x updated: OpenTelemetry Tracer plugin for ATS (#8778)
This is an automated email from the ASF dual-hosted git repository.
zwoop pushed a commit to branch 9.2.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/9.2.x by this push:
new 6ff0fcea6 OpenTelemetry Tracer plugin for ATS (#8778)
6ff0fcea6 is described below
commit 6ff0fcea6df3266ee3d0cfd7ed16146bef0c9d7a
Author: Kit Chan <ki...@apache.org>
AuthorDate: Tue Apr 19 09:14:49 2022 -0700
OpenTelemetry Tracer plugin for ATS (#8778)
* Create otel_tracer.cc
* Create header file
* Create Makefile.inc
* Update Makefile.am
* Update configure.ac
* Update index.en.rst
* Create otel_tracer.en.txt
* Add compile instructions
* Rename otel_tracer.en.txt to otel_tracer.en.rst
* Update configure.ac
* clang-formatted
* Updates
* fix clang format errors
* fix clang format errors
* Updated according to reviews/feedbacks
* fix clang-format
* add txn id to span attributes
* explicitly check variables before releasing them
* update the null checks
* Add debug statements, remove txn id from span attributes, cleanup NULL checks
* fix typo
(cherry picked from commit 6ad81f36a6e9faa368ea6b33cc05a2f3669c22b5)
---
configure.ac | 25 ++
doc/admin-guide/plugins/index.en.rst | 4 +
doc/admin-guide/plugins/otel_tracer.en.rst | 114 +++++++
plugins/Makefile.am | 4 +
plugins/experimental/otel_tracer/Makefile.inc | 26 ++
plugins/experimental/otel_tracer/otel_tracer.cc | 378 +++++++++++++++++++++++
plugins/experimental/otel_tracer/tracer_common.h | 203 ++++++++++++
7 files changed, 754 insertions(+)
diff --git a/configure.ac b/configure.ac
index af8aba872..850b5c547 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1777,6 +1777,31 @@ AS_IF([test "x$geo_provider" = "xauto"], [
AC_SUBST(use_hrw_geoip)
AC_SUBST(use_hrw_maxminddb)
+# Checking if opentelemetry OTLP is available
+AC_LANG_PUSH([C++])
+AC_CHECK_HEADERS([opentelemetry/trace/provider.h], [
+ AC_CHECK_LIB([curl], [curl_version], [
+ AC_CHECK_LIB([protobuf], [main], [
+ AC_CHECK_LIB([opentelemetry_exporter_otlp_http], [main], [
+ AC_SUBST([OTEL_LIBS], [" -lopentelemetry_exporter_otlp_http -lopentelemetry_exporter_otlp_http_client -lopentelemetry_otlp_recordable -lopentelemetry_proto -lopentelemetry_trace -lopentelemetry_resources -lopentelemetry_version -lopentelemetry_common -lhttp_client_curl -lcurl -lprotobuf "])
+ AC_SUBST(has_otel, 1)
+ ], [
+ AC_SUBST([OTEL_LIBS], [""])
+ AC_SUBST(has_otel, 0)
+ ])
+ ], [
+ AC_SUBST([OTEL_LIBS], [""])
+ AC_SUBST(has_otel, 0)
+ ])
+ ], [
+ AC_SUBST([OTEL_LIBS], [""])
+ AC_SUBST(has_otel, 0)
+ ])
+])
+
+AM_CONDITIONAL([HAS_OTEL], [test "x${has_otel}" = "x1" ])
+AC_LANG_POP([C++])
+
# Right now, the healthcheck plugins requires inotify_init (and friends)
AM_CONDITIONAL([BUILD_HEALTHCHECK_PLUGIN], [ test "$ac_cv_func_inotify_init" = "yes" ])
diff --git a/doc/admin-guide/plugins/index.en.rst b/doc/admin-guide/plugins/index.en.rst
index 92d1d32a2..94e71e4a0 100644
--- a/doc/admin-guide/plugins/index.en.rst
+++ b/doc/admin-guide/plugins/index.en.rst
@@ -169,6 +169,7 @@ directory of the |TS| source tree. Experimental plugins can be compiled by passi
MP4 <mp4.en>
Multiplexer <multiplexer.en>
MySQL Remap <mysql_remap.en>
+ OpenTelemetry Tracer <otel_tracer.en>
Parent Select <parent_select.en>
Rate Limit <rate_limit.en>
URI Signing <uri_signing.en>
@@ -234,6 +235,9 @@ directory of the |TS| source tree. Experimental plugins can be compiled by passi
:doc:`MySQL Remap <mysql_remap.en>`
Allows dynamic remaps from a MySQL database.
+:doc:`OpenTelemetry Tracer <otel_tracer.en>`
+ Allows Trafficserver to participate in OpenTelemetry distributed tracing system
+
:doc:`Prefetch <prefetch.en>`
Pre-fetch objects based on the requested URL path pattern.
diff --git a/doc/admin-guide/plugins/otel_tracer.en.rst b/doc/admin-guide/plugins/otel_tracer.en.rst
new file mode 100644
index 000000000..9e05901cf
--- /dev/null
+++ b/doc/admin-guide/plugins/otel_tracer.en.rst
@@ -0,0 +1,114 @@
+.. Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed
+ with this work for additional information regarding copyright
+ ownership. The ASF licenses this file to you under the Apache
+ License, Version 2.0 (the "License"); you may not use this file
+ except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied. See the License for the specific language governing
+ permissions and limitations under the License.
+
+.. _admin-plugins-otel-tracer:
+
+
+OpenTelemetry Tracer Plugin
+***************************
+
+Description
+===========
+
+This plugin allows ATS to participate in OpenTelemetry distributed tracing system.
+The plugin attaches B3 headers to the transaction and sends trace information to an OTLP HTTP endpoint, typically provided through a OpenTelemetry collector.
+
+See the documentation on OpenTelemetry at https://opentelemetry.io/docs/.
+
+How it Works
+============
+
+This plugin checks incoming requests for the B3 headers.
+If they are present, the plugin will participate in the same trace and generate a new span ID.
+If the header is not present, the plugin will generate new trace ID and span ID.
+These information will be added as request headers for transaction going upstream.
+Also together with transaction information such as incoming URL, method, response code, etc, it will be sent to a OTLP HTTP endpoint.
+
+Sample B3 headers:
+
+::
+
+ X-B3-Sampled: 1
+ X-B3-SpanId: 2e56605382810dc9
+ X-B3-TraceId: b9e48850a375247c2c416174648844f4
+
+Compiling the Plugin
+====================
+
+To compile this plugin, we need nlohmann-json, protobuf and opentelemetry-cpp
+
+nlohmann-json:
+
+::
+
+ cd
+ wget https://github.com/nlohmann/json/archive/refs/tags/v3.9.1.tar.gz
+ tar zxvf v3.9.1.tar.gz
+ cd json-3.9.1
+ mkdir build
+ cd build
+ cmake .. -DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD_REQUIRED=ON
+ make
+ make install
+
+protobuf:
+
+::
+
+ cd
+ wget https://github.com/protocolbuffers/protobuf/archive/refs/tags/v3.19.4.tar.gz
+ tar zxvf v3.19.4.tar.gz
+ cd protobuf-3.19.4
+ ./autogen.sh
+ ./configure --enable-shared=no --enable-static=yes CXXFLAGS="-std=c++17 -fPIC" CFLAGS="-fPIC"
+ make
+ make install
+
+opentelemetry-cpp
+
+::
+
+ cd
+ wget https://github.com/open-telemetry/opentelemetry-cpp/archive/refs/tags/v1.2.0.tar.gz
+ tar zxvf v1.2.0.tar.gz
+ cd opentelemetry-cpp-1.2.0
+ mkdir build
+ cd build
+ cmake .. -DBUILD_TESTING=OFF -DWITH_EXAMPLES=OFF -DWITH_JAEGER=OFF -DWITH_OTLP=ON -DWITH_OTLP_GRPC=OFF -DWITH_OTLP_HTTP=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD_REQUIRED=ON
+ cmake --build . --target all
+ cmake --install . --config Debug --prefix /usr/local/
+
+Installation
+============
+
+The `OpenTelemetry Tracer` plugin is a :term:`global plugin`. Enable it by adding ``otel_tracer.so`` to your :file:`plugin.config`.
+
+Configuration
+=============
+
+The plugin supports the following options:
+
+* ``-u=[OTLP HTTP url endpoint]`` (default: ``http://localhost:4317/v1/traces``)
+
+This is the OTLP HTTP url endpoint, typically provided by a OpenTelemetry collector.
+
+* ``-s=[service name]`` (default: ``otel_tracer``)
+
+This is the service name that will be sent as part of the information to the OTLP HTTP url endpoint.
+
+* ``-r=[sampling rate]`` (default: ``1.0``)
+
+The value can be between 0.0 to 1.0. It controls the sampling rate of the trace information.
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 16bc11569..035b8ce0d 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -79,6 +79,10 @@ if HAS_MAXMINDDB
include experimental/maxmind_acl/Makefile.inc
endif
+if HAS_OTEL
+include experimental/otel_tracer/Makefile.inc
+endif
+
include experimental/memcache/Makefile.inc
include experimental/memory_profile/Makefile.inc
include experimental/metalink/Makefile.inc
diff --git a/plugins/experimental/otel_tracer/Makefile.inc b/plugins/experimental/otel_tracer/Makefile.inc
new file mode 100644
index 000000000..bbcdb4107
--- /dev/null
+++ b/plugins/experimental/otel_tracer/Makefile.inc
@@ -0,0 +1,26 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+pkglib_LTLIBRARIES += experimental/otel_tracer/otel_tracer.la
+
+experimental_otel_tracer_otel_tracer_la_SOURCES = \
+ experimental/otel_tracer/otel_tracer.cc \
+ experimental/otel_tracer/tracer_common.h
+
+experimental_otel_tracer_otel_tracer_la_LIBADD = $(OTEL_LIBS)
+
+experimental_otel_tracer_otel_tracer_la_LDFLAGS = \
+ $(AM_LDFLAGS)
diff --git a/plugins/experimental/otel_tracer/otel_tracer.cc b/plugins/experimental/otel_tracer/otel_tracer.cc
new file mode 100644
index 000000000..30b0324bb
--- /dev/null
+++ b/plugins/experimental/otel_tracer/otel_tracer.cc
@@ -0,0 +1,378 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <iostream>
+#include <vector>
+#include <string_view>
+#include <sstream>
+#include <unistd.h>
+#include <getopt.h>
+
+#include "ts/ts.h"
+
+#define PLUGIN_NAME "otel_tracer"
+
+constexpr std::string_view ua_key = {"User-Agent"};
+constexpr std::string_view host_key = {"Host"};
+constexpr std::string_view l_host_key = {"host"};
+constexpr std::string_view b3_key = {"b3"};
+constexpr std::string_view b3_tid_key = {"X-B3-TraceId"};
+constexpr std::string_view b3_sid_key = {"X-B3-SpanId"};
+constexpr std::string_view b3_s_key = {"X-B3-Sampled"};
+
+#include "tracer_common.h"
+
+static int
+close_txn(TSCont contp, TSEvent event, void *edata)
+{
+ int retval = 0;
+ TSMBuffer buf;
+ TSMLoc hdr_loc;
+
+ TSDebug(PLUGIN_NAME, "[%s] Retrieving status code to add to span attributes", __FUNCTION__);
+ auto req_data = static_cast<ExtraRequestData *>(TSContDataGet(contp));
+
+ TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
+
+ if (event != TS_EVENT_HTTP_TXN_CLOSE) {
+ TSError("[otel_tracer][%s] Unexpected event (%d)", __FUNCTION__, event);
+ goto lReturn;
+ }
+
+ if (TSHttpTxnClientRespGet(txnp, &buf, &hdr_loc) == TS_SUCCESS) {
+ int status = TSHttpHdrStatusGet(buf, hdr_loc);
+ req_data->SetSpanStatus(req_data, status);
+ if (status > 499) {
+ req_data->SetSpanError(req_data);
+ }
+
+ TSHandleMLocRelease(buf, TS_NULL_MLOC, hdr_loc);
+ }
+
+ retval = 1;
+
+lReturn:
+ TSDebug(PLUGIN_NAME, "[%s] Cleaning up after close hook handler", __FUNCTION__);
+ req_data->Destruct(req_data);
+ delete req_data;
+
+ TSContDestroy(contp);
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+
+ return retval;
+}
+
+static void
+set_request_header(TSMBuffer buf, TSMLoc hdr_loc, const char *key, int key_len, const char *val, int val_len)
+{
+ TSMLoc field_loc = TSMimeHdrFieldFind(buf, hdr_loc, key, key_len);
+
+ if (field_loc != TS_NULL_MLOC) {
+ int first = 1;
+ while (field_loc != TS_NULL_MLOC) {
+ TSMLoc tmp = TSMimeHdrFieldNextDup(buf, hdr_loc, field_loc);
+ if (first) {
+ first = 0;
+ TSMimeHdrFieldValueStringSet(buf, hdr_loc, field_loc, -1, val, val_len);
+ } else {
+ TSMimeHdrFieldDestroy(buf, hdr_loc, field_loc);
+ }
+ TSHandleMLocRelease(buf, hdr_loc, field_loc);
+ field_loc = tmp;
+ }
+ } else if (TSMimeHdrFieldCreateNamed(buf, hdr_loc, key, key_len, &field_loc) != TS_SUCCESS) {
+ TSError("[otel_tracer][%s] TSMimeHdrFieldCreateNamed error", __FUNCTION__);
+ return;
+ } else {
+ TSMimeHdrFieldValueStringSet(buf, hdr_loc, field_loc, -1, val, val_len);
+ TSMimeHdrFieldAppend(buf, hdr_loc, field_loc);
+ }
+
+ if (field_loc != TS_NULL_MLOC) {
+ TSHandleMLocRelease(buf, hdr_loc, field_loc);
+ }
+
+ return;
+}
+
+static void
+read_request(TSHttpTxn txnp, TSCont contp)
+{
+ TSMBuffer buf;
+ TSMLoc hdr_loc;
+
+ TSDebug(PLUGIN_NAME, "[%s] Reading information from request", __FUNCTION__);
+ if (TSHttpTxnClientReqGet(txnp, &buf, &hdr_loc) != TS_SUCCESS) {
+ TSError("[otel_tracer][%s] cannot retrieve client request", __FUNCTION__);
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return;
+ }
+
+ // path, host, port, scheme
+ TSMLoc url_loc = nullptr;
+ if (TSHttpHdrUrlGet(buf, hdr_loc, &url_loc) != TS_SUCCESS) {
+ TSError("[otel_tracer][%s] cannot retrieve client request url", __FUNCTION__);
+ TSHandleMLocRelease(buf, TS_NULL_MLOC, hdr_loc);
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return;
+ }
+
+ std::string path_str = "/";
+ const char *path = nullptr;
+ int path_len = 0;
+ path = TSUrlPathGet(buf, url_loc, &path_len);
+ path_str.append(std::string_view(path, path_len));
+
+ TSMLoc host_field_loc = TS_NULL_MLOC;
+ TSMLoc l_host_field_loc = TS_NULL_MLOC;
+ const char *host = nullptr;
+ int host_len = 0;
+ host = TSUrlHostGet(buf, url_loc, &host_len);
+ if (host_len == 0) {
+ host_field_loc = TSMimeHdrFieldFind(buf, hdr_loc, host_key.data(), host_key.length());
+ if (host_field_loc != TS_NULL_MLOC) {
+ host = TSMimeHdrFieldValueStringGet(buf, hdr_loc, host_field_loc, -1, &host_len);
+ }
+
+ if (host_len == 0) {
+ l_host_field_loc = TSMimeHdrFieldFind(buf, hdr_loc, l_host_key.data(), l_host_key.length());
+ if (l_host_field_loc != TS_NULL_MLOC) {
+ host = TSMimeHdrFieldValueStringGet(buf, hdr_loc, l_host_field_loc, -1, &host_len);
+ }
+ }
+ }
+ std::string_view host_str(host, host_len);
+
+ const char *scheme = nullptr;
+ int scheme_len = 0;
+ scheme = TSUrlSchemeGet(buf, url_loc, &scheme_len);
+ std::string_view scheme_str(scheme, scheme_len);
+
+ int port;
+ port = TSUrlPortGet(buf, url_loc);
+
+ // method
+ const char *method = nullptr;
+ int method_len = 0;
+ method = TSHttpHdrMethodGet(buf, hdr_loc, &method_len);
+ std::string_view method_str(method, method_len);
+
+ // TO-DO: add http flavor as attribute
+ // const char *h2_tag = TSHttpTxnClientProtocolStackContains(txnp, "h2");
+ // const char *h1_tag = TSHttpTxnClientProtocolStackContains(txnp, "http/1.0");
+ // const char *h11_tag = TSHttpTxnClientProtocolStackContains(txnp, "http/1.1");
+
+ // target
+ char *target = nullptr;
+ int target_len = 0;
+ target = TSHttpTxnEffectiveUrlStringGet(txnp, &target_len);
+ std::string_view target_str(target, target_len);
+
+ // user-agent
+ TSMLoc ua_field_loc = TSMimeHdrFieldFind(buf, hdr_loc, ua_key.data(), ua_key.length());
+ const char *ua = nullptr;
+ int ua_len = 0;
+ if (ua_field_loc) {
+ ua = TSMimeHdrFieldValueStringGet(buf, hdr_loc, ua_field_loc, -1, &ua_len);
+ }
+ std::string_view ua_str(ua, ua_len);
+
+ // B3 headers
+ TSMLoc b3_field_loc = TSMimeHdrFieldFind(buf, hdr_loc, b3_key.data(), b3_key.length());
+ const char *b3 = nullptr;
+ int b3_len = 0;
+ if (b3_field_loc) {
+ b3 = TSMimeHdrFieldValueStringGet(buf, hdr_loc, b3_field_loc, -1, &b3_len);
+ }
+ std::string_view b3_str(b3, b3_len);
+
+ TSMLoc b3_tid_field_loc = TSMimeHdrFieldFind(buf, hdr_loc, b3_tid_key.data(), b3_tid_key.length());
+ const char *b3_tid = nullptr;
+ int b3_tid_len = 0;
+ if (b3_tid_field_loc) {
+ b3_tid = TSMimeHdrFieldValueStringGet(buf, hdr_loc, b3_tid_field_loc, -1, &b3_tid_len);
+ }
+ std::string_view b3_tid_str(b3_tid, b3_tid_len);
+
+ TSMLoc b3_sid_field_loc = TSMimeHdrFieldFind(buf, hdr_loc, b3_sid_key.data(), b3_sid_key.length());
+ const char *b3_sid = nullptr;
+ int b3_sid_len = 0;
+ if (b3_sid_field_loc) {
+ b3_sid = TSMimeHdrFieldValueStringGet(buf, hdr_loc, b3_sid_field_loc, -1, &b3_sid_len);
+ }
+ std::string_view b3_sid_str(b3_sid, b3_sid_len);
+
+ TSMLoc b3_s_field_loc = TSMimeHdrFieldFind(buf, hdr_loc, b3_s_key.data(), b3_s_key.length());
+ const char *b3_s = nullptr;
+ int b3_s_len = 0;
+ if (b3_s_field_loc) {
+ b3_s = TSMimeHdrFieldValueStringGet(buf, hdr_loc, b3_s_field_loc, -1, &b3_s_len);
+ }
+ std::string_view b3_s_str(b3_s, b3_s_len);
+
+ // TODO: add remote ip, port to attributes
+
+ // create parent context
+ TSDebug(PLUGIN_NAME, "[%s] Creating parent context from incoming request headers", __FUNCTION__);
+ std::map<std::string, std::string> parent_headers;
+ if (b3_len != 0) {
+ parent_headers[std::string{b3_key}] = b3_str;
+ }
+ if (b3_tid_len != 0) {
+ parent_headers[std::string{b3_tid_key}] = b3_tid_str;
+ }
+ if (b3_sid_len != 0) {
+ parent_headers[std::string{b3_sid_key}] = b3_sid_str;
+ }
+ if (b3_s_len != 0) {
+ parent_headers[std::string{b3_s_key}] = b3_s_str;
+ }
+
+ // create trace span and activate
+ TSDebug(PLUGIN_NAME, "[%s] Create span with a name, attributes, parent context and activate it", __FUNCTION__);
+ auto span = get_tracer("ats")->StartSpan(
+ get_span_name(path_str), get_span_attributes(method_str, target_str, path_str, host_str, ua_str, port, scheme_str),
+ get_span_options(parent_headers));
+
+ auto scope = get_tracer("ats")->WithActiveSpan(span);
+
+ std::map<std::string, std::string> trace_headers = get_trace_headers();
+ // insert headers to request
+ TSDebug(PLUGIN_NAME, "[%s] Insert trace headers to upstream request", __FUNCTION__);
+ for (auto &p : trace_headers) {
+ set_request_header(buf, hdr_loc, p.first.c_str(), p.first.size(), p.second.c_str(), p.second.size());
+ }
+
+ // pass the span
+ TSDebug(PLUGIN_NAME, "[%s] Add close hook to add status code to span attribute", __FUNCTION__);
+ TSCont close_txn_contp = TSContCreate(close_txn, nullptr);
+ if (!close_txn_contp) {
+ TSError("[otel_tracer][%s] Could not create continuation", __FUNCTION__);
+ } else {
+ TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, close_txn_contp);
+ ExtraRequestData *req_data = new ExtraRequestData;
+ req_data->span = span;
+ TSContDataSet(close_txn_contp, req_data);
+ }
+
+ // clean up
+ TSDebug(PLUGIN_NAME, "[%s] Cleanig up", __FUNCTION__);
+ if (target != nullptr) {
+ TSfree(target);
+ }
+ if (host_field_loc != TS_NULL_MLOC) {
+ TSHandleMLocRelease(buf, hdr_loc, host_field_loc);
+ }
+ if (l_host_field_loc != TS_NULL_MLOC) {
+ TSHandleMLocRelease(buf, hdr_loc, l_host_field_loc);
+ }
+ if (ua_field_loc != TS_NULL_MLOC) {
+ TSHandleMLocRelease(buf, hdr_loc, ua_field_loc);
+ }
+ if (b3_field_loc != TS_NULL_MLOC) {
+ TSHandleMLocRelease(buf, hdr_loc, b3_field_loc);
+ }
+ if (b3_tid_field_loc != TS_NULL_MLOC) {
+ TSHandleMLocRelease(buf, hdr_loc, b3_tid_field_loc);
+ }
+ if (b3_sid_field_loc != TS_NULL_MLOC) {
+ TSHandleMLocRelease(buf, hdr_loc, b3_sid_field_loc);
+ }
+ if (b3_s_field_loc != TS_NULL_MLOC) {
+ TSHandleMLocRelease(buf, hdr_loc, b3_s_field_loc);
+ }
+ if (url_loc != nullptr) {
+ TSHandleMLocRelease(buf, hdr_loc, url_loc);
+ }
+ TSHandleMLocRelease(buf, TS_NULL_MLOC, hdr_loc);
+
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+}
+
+static int
+plugin_handler(TSCont contp, TSEvent event, void *edata)
+{
+ TSHttpTxn txnp = (TSHttpTxn)edata;
+
+ switch (event) {
+ case TS_EVENT_HTTP_READ_REQUEST_HDR:
+ read_request(txnp, contp);
+ return 0;
+ default:
+ break;
+ }
+ return 0;
+}
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+ TSPluginRegistrationInfo info;
+
+ info.plugin_name = PLUGIN_NAME;
+ info.vendor_name = "Apache Software Foundation";
+ info.support_email = "dev@trafficserver.apache.org";
+
+ // Get parameter: service name, sampling rate,
+ std::string url = "";
+ std::string service_name = "otel_tracer";
+ double rate = 1.0;
+ if (argc > 1) {
+ int c;
+ static const struct option longopts[] = {
+ {const_cast<char *>("url"), required_argument, nullptr, 'u'},
+ {const_cast<char *>("service-name"), required_argument, nullptr, 's'},
+ {const_cast<char *>("sampling-rate"), required_argument, nullptr, 'r'},
+ {nullptr, 0, nullptr, 0},
+ };
+
+ int longindex = 0;
+ while ((c = getopt_long(argc, const_cast<char *const *>(argv), "u:s:r:", longopts, &longindex)) != -1) {
+ switch (c) {
+ case 'u':
+ url = optarg;
+ break;
+ case 's':
+ service_name = optarg;
+ break;
+ case 'r':
+ rate = atof(optarg);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ InitTracer(url, service_name, rate);
+
+ if (TSPluginRegister(&info) != TS_SUCCESS) {
+ TSError("[%s] Plugin registration failed", PLUGIN_NAME);
+ goto error;
+ }
+
+ TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, TSContCreate(plugin_handler, nullptr));
+ goto done;
+
+error:
+ TSError("[%s] Plugin not initialized", PLUGIN_NAME);
+
+done:
+ TSDebug(PLUGIN_NAME, "Plugin initialized");
+ return;
+}
diff --git a/plugins/experimental/otel_tracer/tracer_common.h b/plugins/experimental/otel_tracer/tracer_common.h
new file mode 100644
index 000000000..d4caf573c
--- /dev/null
+++ b/plugins/experimental/otel_tracer/tracer_common.h
@@ -0,0 +1,203 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#pragma once
+#include "opentelemetry/exporters/otlp/otlp_http_exporter.h"
+#include "opentelemetry/sdk/trace/batch_span_processor.h"
+#include "opentelemetry/sdk/trace/span_data.h"
+#include "opentelemetry/trace/propagation/jaeger.h"
+#include "opentelemetry/trace/propagation/b3_propagator.h"
+
+#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/trace/propagation/http_trace_context.h"
+
+#include "opentelemetry/sdk/trace/samplers/always_off.h"
+#include "opentelemetry/sdk/trace/samplers/always_on.h"
+#include "opentelemetry/sdk/trace/samplers/parent.h"
+#include "opentelemetry/sdk/trace/samplers/trace_id_ratio.h"
+
+#include <cstring>
+#include <iostream>
+#include <vector>
+#include <string>
+#include "opentelemetry/ext/http/client/http_client.h"
+#include "opentelemetry/nostd/shared_ptr.h"
+
+namespace trace = opentelemetry::trace;
+namespace nostd = opentelemetry::nostd;
+namespace sdktrace = opentelemetry::sdk::trace;
+namespace context = opentelemetry::context;
+namespace otlp = opentelemetry::exporter::otlp;
+namespace
+{
+constexpr const char *attrHttpStatusCode = {"http.status_code"};
+constexpr const char *attrHttpMethod = {"http.method"};
+constexpr const char *attrHttpUrl = {"http.url"};
+constexpr const char *attrHttpRoute = {"http.route"};
+constexpr const char *attrHttpHost = {"http.host"};
+constexpr const char *attrHttpUserAgent = {"http.user_agent"};
+constexpr const char *attrNetHostPort = {"net.host.port"};
+constexpr const char *attrHttpScheme = {"http.scheme"};
+
+template <typename T> class HttpTextMapCarrier : public context::propagation::TextMapCarrier
+{
+public:
+ HttpTextMapCarrier<T>(T &headers) : headers_(headers) {}
+ HttpTextMapCarrier() = default;
+ virtual nostd::string_view
+ Get(nostd::string_view key) const noexcept override
+ {
+ std::string key_to_compare = key.data();
+ auto it = headers_.find(key_to_compare);
+ if (it != headers_.end()) {
+ return it->second;
+ }
+ return "";
+ }
+
+ virtual void
+ Set(nostd::string_view key, nostd::string_view value) noexcept override
+ {
+ headers_.insert(std::pair<std::string, std::string>(std::string(key), std::string(value)));
+ }
+
+ T headers_;
+};
+
+// this object is created using placement new therefore all destructors needs
+// to be called explictly inside Destruct method
+struct ExtraRequestData {
+ nostd::shared_ptr<trace::Span> span;
+
+ static void
+ SetSpanStatus(ExtraRequestData *This, int status)
+ {
+ This->span->SetAttribute(attrHttpStatusCode, status);
+ }
+
+ static void
+ SetSpanError(ExtraRequestData *This)
+ {
+ This->span->SetStatus(trace::StatusCode::kError);
+ }
+
+ static int
+ Destruct(ExtraRequestData *This)
+ {
+ if (This->span) {
+ This->span->End();
+ This->span = nullptr;
+ }
+ return 0;
+ }
+};
+
+void
+InitTracer(std::string url, std::string service_name, double rate)
+{
+ otlp::OtlpHttpExporterOptions opts;
+
+ if (url != "") {
+ opts.url = url;
+ }
+
+ auto exporter = std::unique_ptr<sdktrace::SpanExporter>(new otlp::OtlpHttpExporter(opts));
+ auto processor = std::unique_ptr<sdktrace::SpanProcessor>(new sdktrace::SimpleSpanProcessor(std::move(exporter)));
+
+ std::vector<std::unique_ptr<sdktrace::SpanProcessor>> processors;
+ processors.push_back(std::move(processor));
+
+ // Set service name
+ opentelemetry::sdk::resource::ResourceAttributes attributes = {{"service.name", service_name}, {"version", (uint32_t)1}};
+ auto resource = opentelemetry::sdk::resource::Resource::Create(attributes);
+
+ auto context = std::make_shared<sdktrace::TracerContext>(std::move(processors), resource,
+ std::unique_ptr<sdktrace::Sampler>(new sdktrace::ParentBasedSampler(
+ std::make_shared<sdktrace::TraceIdRatioBasedSampler>(rate))));
+ auto provider = nostd::shared_ptr<trace::TracerProvider>(new sdktrace::TracerProvider(context));
+
+ // Set the global trace provider
+ trace::Provider::SetTracerProvider(provider);
+
+ // format: b3
+ context::propagation::GlobalTextMapPropagator::SetGlobalPropagator(
+ nostd::shared_ptr<context::propagation::TextMapPropagator>(new trace::propagation::B3PropagatorMultiHeader()));
+}
+
+nostd::shared_ptr<trace::Tracer>
+get_tracer(std::string tracer_name)
+{
+ auto provider = trace::Provider::GetTracerProvider();
+ return provider->GetTracer(tracer_name, OPENTELEMETRY_SDK_VERSION);
+}
+
+nostd::string_view
+get_span_name(std::string_view path_str)
+{
+ nostd::string_view span_name(path_str.data(), path_str.length());
+ return span_name;
+}
+
+std::map<nostd::string_view, opentelemetry::common::AttributeValue>
+get_span_attributes(std::string_view method_str, std::string_view target_str, std::string_view path_str, std::string_view host_str,
+ std::string_view ua_str, int port, std::string_view scheme_str)
+{
+ std::map<nostd::string_view, opentelemetry::common::AttributeValue> attributes;
+ attributes[attrHttpMethod] = nostd::string_view(method_str.data(), method_str.length());
+ attributes[attrHttpUrl] = nostd::string_view(target_str.data(), target_str.length());
+ attributes[attrHttpRoute] = nostd::string_view(path_str.data(), path_str.length());
+ attributes[attrHttpHost] = nostd::string_view(host_str.data(), host_str.length());
+ attributes[attrHttpUserAgent] = nostd::string_view(ua_str.data(), ua_str.length());
+ attributes[attrNetHostPort] = port;
+ attributes[attrHttpScheme] = nostd::string_view(scheme_str.data(), scheme_str.length());
+
+ return attributes;
+}
+
+trace::StartSpanOptions
+get_span_options(std::map<std::string, std::string> parent_headers)
+{
+ trace::StartSpanOptions options;
+
+ options.kind = trace::SpanKind::kServer; // server
+
+ const HttpTextMapCarrier<std::map<std::string, std::string>> parent_carrier(parent_headers);
+ auto parent_prop = context::propagation::GlobalTextMapPropagator::GetGlobalPropagator();
+ auto parent_current_ctx = context::RuntimeContext::GetCurrent();
+ auto parent_new_context = parent_prop->Extract(parent_carrier, parent_current_ctx);
+ options.parent = trace::GetSpan(parent_new_context)->GetContext();
+
+ return options;
+}
+
+std::map<std::string, std::string>
+get_trace_headers()
+{
+ auto current_ctx = opentelemetry::context::RuntimeContext::GetCurrent();
+ HttpTextMapCarrier<std::map<std::string, std::string>> carrier;
+ auto prop = opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator();
+ prop->Inject(carrier, current_ctx);
+
+ return carrier.headers_;
+}
+
+} // namespace