diff --git a/instrumentation/httpd/README.md b/instrumentation/httpd/README.md index 0395e265d..7c3cd7d5d 100644 --- a/instrumentation/httpd/README.md +++ b/instrumentation/httpd/README.md @@ -14,6 +14,31 @@ For manual build please check below. Otherwise please use one of the [released versions](/../releases). +### From Docker Image + +To start using mod_otel you can add it following way: +```Dockerfile +FROM httpd +ADD https://github.com/open-telemetry/opentelemetry-cpp-contrib/releases/download/httpd%2Fv0.1.0/ubuntu-20.04_mod-otel.so.zip /tmp +ADD https://raw.githubusercontent.com/open-telemetry/opentelemetry-cpp-contrib/main/instrumentation/httpd/opentelemetry.conf /usr/local/apache2/conf/extra/ + +RUN mv /tmp/ubuntu-20.04_mod-otel.so.zip /usr/local/apache2/modules/mod_otel.so.gz +RUN gzip -d /usr/local/apache2/modules/mod_otel.so.gz + +RUN echo "LoadFile /usr/lib/x86_64-linux-gnu/libstdc++.so.6" >> /usr/local/apache2/conf/httpd.conf +RUN echo "LoadModule otel_module modules/mod_otel.so" >> /usr/local/apache2/conf/httpd.conf +RUN echo "Include conf/extra/opentelemetry.conf" >> /usr/local/apache2/conf/httpd.conf +``` + +### Logging traces + +Once module is enabled you have access to currently processed span via environment variables. Those can be accessed under: +- `%{OTEL_SPANID}e` for SpanID +- `%{OTEL_TRACEID}e` for TraceID +- `%{OTEL_TRACEFLAGS}e` for TraceID +- `%{OTEL_TRACESTATE}e` for TraceState +when using [LogFormat directive](https://httpd.apache.org/docs/2.4/mod/mod_log_config.html#formats). + ### Installation Mod_otel works as a module which is loaded when Apache starts. It is written in C++ therefore standard library has to be included as well. Below is an example of lines which should be added to your configuration file (usually `/etc/httpd/conf.d` or equivalent): @@ -91,7 +116,7 @@ When local changes are made, you need to restart the `httpd` server to load new ### Prerequisites (Ubuntu) -On Ubuntu you need packages listed here: [setup_environment.sh](./setup_environment.sh) which are prerequisites to compile opentelemetry-cpp and here: [setup-buildtools.sh](./setup-buildtools.sh) for apache development stuff. Then just execute [bulid.sh](./build.sh). +On Ubuntu you need packages listed here: [setup-environment.sh](./setup-environment.sh) which are prerequisites to compile opentelemetry-cpp and here: [setup-buildtools.sh](./setup-buildtools.sh) for apache development stuff. Then just execute [bulid.sh](./build.sh). ### Run formatting check diff --git a/instrumentation/httpd/create-otel-load.sh b/instrumentation/httpd/create-otel-load.sh index 44282e55b..2407440ad 100755 --- a/instrumentation/httpd/create-otel-load.sh +++ b/instrumentation/httpd/create-otel-load.sh @@ -2,10 +2,28 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +POSSIBLE_BUILD_OUTPUT=() +POSSIBLE_BUILD_OUTPUT+=("${SCRIPT_DIR}/bazel-out/k8-opt/bin/otel.so") # when build was done with Bazel +POSSIBLE_BUILD_OUTPUT+=("${SCRIPT_DIR}/build/otel_httpd_module.so") # when build was done with CMake + +for LOCATE_OUTPUT in "${POSSIBLE_BUILD_OUTPUT[@]}"; do + if [ -f ${LOCATE_OUTPUT} ]; then + FOUND=${LOCATE_OUTPUT} + echo Found file ${FOUND} + break + fi +done + +if [ -z ${FOUND+x} ]; then + echo "Binary module not found!" + echo "Please run make build-cmake or make build-bazel first" + exit 1 +fi + # create configuration file for httpd (Apache) cat << EOF > opentelemetry.load # C++ Standard library LoadFile /usr/lib/x86_64-linux-gnu/libstdc++.so.6 -LoadModule otel_module $SCRIPT_DIR/bazel-out/k8-opt/bin/otel.so +LoadModule otel_module $FOUND EOF diff --git a/instrumentation/httpd/src/otel/mod_otel.cpp b/instrumentation/httpd/src/otel/mod_otel.cpp index a8c85f0bd..3f7b19c45 100644 --- a/instrumentation/httpd/src/otel/mod_otel.cpp +++ b/instrumentation/httpd/src/otel/mod_otel.cpp @@ -37,6 +37,11 @@ using namespace httpd_otel; const char kOpenTelemetryKeyNote[] = "OTEL"; const char kOpenTelemetryKeyOutboundNote[] = "OTEL_PROXY"; +const char kEnvVarSpanId[] = "OTEL_SPANID"; +const char kEnvVarTraceId[] = "OTEL_TRACEID"; +const char kEnvVarTraceFlags[] = "OTEL_TRACEFLAGS"; +const char kEnvVarTraceState[] = "OTEL_TRACESTATE"; + class HttpdCarrier : public opentelemetry::context::propagation::TextMapCarrier { public: @@ -99,6 +104,28 @@ static void opentel_child_created(apr_pool_t*, server_rec*) initTracer(); } +// populates environment variables which can be used by other parts of httpd +void addEnvVars(apr_table_t *envTable, opentelemetry::trace::SpanContext ctx) +{ + union { + char bfr[33] = {0}; + char spanId[16]; + char traceId[32]; + } ; + + if (ctx.trace_flags().IsSampled()) + { + apr_table_set(envTable, kEnvVarTraceFlags, "1"); + } + // to keep bfr null-terminated we start from shorter (spanId) + ctx.span_id().ToLowerBase16(spanId); + apr_table_set(envTable, kEnvVarSpanId, bfr); + ctx.trace_id().ToLowerBase16(traceId); + apr_table_set(envTable, kEnvVarTraceId, bfr); + + apr_table_set(envTable, kEnvVarTraceState, ctx.trace_state()->ToHeader().c_str()); +} + // Starting span as early as possible (quick handler): // http://www.fmc-modeling.org/category/projects/apache/amp/3_3Extending_Apache.html#fig:_Apache:_request-processing_+_Module_callbacks_PN static int opentel_handler(request_rec *r, int /* lookup_uri */ ) @@ -148,6 +175,8 @@ static int opentel_handler(request_rec *r, int /* lookup_uri */ ) req_data->span = span; req_data->StartSpan(GetAttrsFromRequest(req)); + addEnvVars(req->subprocess_env, span->GetContext()); + return DECLINED; } diff --git a/instrumentation/httpd/tests/05-check-batch-spans.sh b/instrumentation/httpd/tests/05-check-batch-spans.sh index 979d85e9e..2bf90d3a2 100755 --- a/instrumentation/httpd/tests/05-check-batch-spans.sh +++ b/instrumentation/httpd/tests/05-check-batch-spans.sh @@ -4,11 +4,6 @@ TEST_NAME="Check that 5 requests creates 5 spans (with batch)" . tools.sh -fail () { - printf '%s\n' "$1" >&2 ## Send message to stderr. Exclude >&2 if you don't want it that way. - exit "${2-1}" ## Return a code specified by $2 or 1 by default. -} - setup_test () { cat << EOF > ${HTTPD_CONFIG} diff --git a/instrumentation/httpd/tests/07-create-outbound-spans.sh b/instrumentation/httpd/tests/07-create-outbound-spans.sh index 848fcbf99..093ee291b 100755 --- a/instrumentation/httpd/tests/07-create-outbound-spans.sh +++ b/instrumentation/httpd/tests/07-create-outbound-spans.sh @@ -65,7 +65,6 @@ run_test() { curl --fail -v -H "${HEADER_1}" -H "${HEADER_2}" ${ENDPOINT_URL}/bar || failHttpd "Unable to download page with proxy enabled" } - check_results() { echo "Checking that exactly two spans were created (client one and server one)" count '{' 2 # total two spans = one incoming and one outgoing diff --git a/instrumentation/httpd/tests/08-create-outbound-spans-b3.sh b/instrumentation/httpd/tests/08-create-outbound-spans-b3.sh index 3cd009adb..e1c8a5a15 100755 --- a/instrumentation/httpd/tests/08-create-outbound-spans-b3.sh +++ b/instrumentation/httpd/tests/08-create-outbound-spans-b3.sh @@ -57,7 +57,6 @@ run_test() { curl --fail -v -H "${HEADER}" ${ENDPOINT_URL}/bar || failHttpd "Unable to download page with proxy enabled" } - check_results() { echo "Checking that exactly two spans were created (client one and server one)" count '{' 2 # total two spans = one incoming and one outgoing diff --git a/instrumentation/httpd/tests/09-check-environment-vars.sh b/instrumentation/httpd/tests/09-check-environment-vars.sh new file mode 100755 index 000000000..b97c21edb --- /dev/null +++ b/instrumentation/httpd/tests/09-check-environment-vars.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +TEST_NAME="Check that information about currently processed span is available in environment variables" + +. tools.sh + +EXTRA_LOG_FILE=/tmp/apache-test-$$.log + +setup_test () { + +EXTRA_HTTPD_MODS="headers" + +# here we are setting apache mod_header to rewrite environment +# variables back to response header as an extra check +cat << EOF > ${HTTPD_CONFIG} +OpenTelemetryExporter file +OpenTelemetryPath ${OUTPUT_SPANS} +Header set x-env-spanid %{OTEL_SPANID}e +Header set x-env-traceid %{OTEL_TRACEID}e +Header set x-env-traceflags %{OTEL_TRACEFLAGS}e +Header set x-env-tracestate %{OTEL_TRACESTATE}e +LogFormat "span=%{OTEL_SPANID}e trace=%{OTEL_TRACEID}e flags=%{OTEL_TRACEFLAGS}e state=%{OTEL_TRACESTATE}e" otel_format +GlobalLog ${EXTRA_LOG_FILE} otel_format +EOF + +} + +run_test() { + ${CURL_CMD} ${ENDPOINT_URL} || fail "Unable to download main page" +} + +check_results() { + echo Checking that log file contains information + grep --color -E "span=[0-9a-f]{16}" ${EXTRA_LOG_FILE} || fail "SpanID not found in log file" + grep --color -E "trace=[0-9a-f]{32}" ${EXTRA_LOG_FILE} || fail "TraceID not found in log file" + grep --color -E "flags=1" ${EXTRA_LOG_FILE} || fail "TraceFlags not found in log file" +} + +teardown_test() { + rm -rf ${OUTPUT_SPANS} ${EXTRA_LOG_FILE} +} + +run $@ diff --git a/instrumentation/httpd/tests/tools.sh b/instrumentation/httpd/tests/tools.sh index 39a715e9e..9086e4fa1 100755 --- a/instrumentation/httpd/tests/tools.sh +++ b/instrumentation/httpd/tests/tools.sh @@ -150,7 +150,7 @@ run() { setup_test - # setup proxy if handler function was defined inside test script + # start proxy (netcat based) only when handler function was defined inside test script if type proxy &>/dev/null ; then rm -rf ${PROXY_PID_FILE} $0 start_proxy & @@ -178,6 +178,7 @@ run() { # stop apache - this is important as this flushes span file apache2ctl -k stop -c "Include ${HTTPD_CONFIG}" || failHttpd "Apache stop failed" + # stop proxy if it was used by this test if type proxy &>/dev/null ; then echo "Waiting for proxy to stop (PID $!)" rm -rf ${PROXY_PID_FILE}