You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by ki...@apache.org on 2022/04/19 16:14:55 UTC

[trafficserver] branch master updated: OpenTelemetry Tracer plugin for ATS (#8778)

This is an automated email from the ASF dual-hosted git repository.

kichan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 6ad81f36a OpenTelemetry Tracer plugin for ATS (#8778)
6ad81f36a is described below

commit 6ad81f36a6e9faa368ea6b33cc05a2f3669c22b5
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
---
 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 1745358ed..5c752b310 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1786,6 +1786,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 12ff6ad50..8a8192847 100644
--- a/doc/admin-guide/plugins/index.en.rst
+++ b/doc/admin-guide/plugins/index.en.rst
@@ -165,6 +165,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>
@@ -230,6 +231,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 fff6fdbd6..149c05f55 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -78,6 +78,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