From 85a3fcb38c0c21f3b34877abe171e43bd3256177 Mon Sep 17 00:00:00 2001
From: Pavel Pautov
Date: Tue, 13 Dec 2022 21:32:53 -0800
Subject: [PATCH] Support custom span name and attributes.
---
README.md | 8 ++++
src/batch_exporter.hpp | 10 +++++
src/http_module.cpp | 100 +++++++++++++++++++++++++++++++++++++++--
src/str_view.hpp | 5 +++
4 files changed, 119 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 7bea8e4..38d1448 100644
--- a/README.md
+++ b/README.md
@@ -101,6 +101,14 @@ The argument is a “complex value”, which should result in `on`/`off` or `1`/
Defines how to propagate traceparent/tracestate headers. `extract` uses existing trace context from request. `inject` adds new context to request, rewriting existing headers if any. `propagate` updates existing context (i.e. combines `extract` and `inject`). `ignore` skips context headers processing. Default is `ignore`.
+**`otel_span_name`** `name;`
+
+Default is request’s location name.
+
+**`otel_span_attr`** `name “$var”;`
+
+If name starts with `http.(request|response).header.` the type of added attribute will be `string[]` to match semantic conventions (i.e. header value will be represented as a single element array). Otherwise, the attribute type will be `string`.
+
#### Available in `http` context
**`otel_exporter`**`;`
diff --git a/src/batch_exporter.hpp b/src/batch_exporter.hpp
index eaeb9d4..d160d2c 100644
--- a/src/batch_exporter.hpp
+++ b/src/batch_exporter.hpp
@@ -70,6 +70,16 @@ public:
add(key)->mutable_value()->set_int_value(value);
}
+ void addArray(StrView key, StrView value)
+ {
+ auto elems = add(key)->mutable_value()->mutable_array_value()->
+ mutable_values();
+
+ auto elem = elems->size() > 0 ? elems->Mutable(0) : elems->Add();
+
+ elem->mutable_string_value()->assign(value.data(), value.size());
+ }
+
void setError()
{
span->mutable_status()->set_code(
diff --git a/src/http_module.cpp b/src/http_module.cpp
index f195ff1..8c9252a 100644
--- a/src/http_module.cpp
+++ b/src/http_module.cpp
@@ -26,12 +26,21 @@ struct MainConf {
ngx_str_t serviceName;
};
+struct SpanAttr {
+ ngx_str_t name;
+ ngx_http_complex_value_t value;
+};
+
struct LocationConf {
ngx_http_complex_value_t* trace;
ngx_uint_t traceContext;
+
+ ngx_http_complex_value_t* spanName;
+ ngx_array_t spanAttrs;
};
char* setExporter(ngx_conf_t* cf, ngx_command_t* cmd, void* conf);
+char* addSpanAttr(ngx_conf_t* cf, ngx_command_t* cmd, void* conf);
namespace Propagation {
@@ -74,6 +83,17 @@ ngx_command_t gCommands[] = {
offsetof(LocationConf, traceContext),
&Propagation::Types },
+ { ngx_string("otel_span_name"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_set_complex_value_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(LocationConf, spanName) },
+
+ { ngx_string("otel_span_attr"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ addSpanAttr,
+ NGX_HTTP_LOC_CONF_OFFSET },
+
ngx_null_command
};
@@ -369,6 +389,48 @@ void addDefaultAttrs(BatchExporter::Span& span, ngx_http_request_t* r)
span.add("net.sock.peer.port", ngx_inet_get_port(r->connection->sockaddr));
}
+StrView getSpanName(ngx_http_request_t* r)
+{
+ auto lcf = getLocationConf(r);
+
+ if (lcf->spanName) {
+ ngx_str_t result;
+ if (ngx_http_complex_value(r, lcf->spanName, &result) != NGX_OK) {
+ throw std::runtime_error("failed to compute complex value");
+ }
+
+ return toStrView(result);
+ } else {
+ auto clcf = (ngx_http_core_loc_conf_t*)
+ ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ return toStrView(clcf->name);
+ }
+}
+
+void addCustomAttrs(BatchExporter::Span& span, ngx_http_request_t* r)
+{
+ auto lcf = getLocationConf(r);
+ auto attrs = (SpanAttr*)lcf->spanAttrs.elts;
+
+ for (ngx_uint_t i = 0; i < lcf->spanAttrs.nelts; i++) {
+ ngx_str_t value;
+ if (ngx_http_complex_value(r, &attrs[i].value, &value) != NGX_OK) {
+ throw std::runtime_error("failed to compute complex value");
+ }
+
+ StrView name = toStrView(attrs[i].name);
+ if (startsWith(name, "http.request.header.") ||
+ startsWith(name, "http.response.header."))
+ {
+ //TODO: remove this once headers are supported natively
+ span.addArray(name, toStrView(value));
+ } else {
+ span.add(name, toStrView(value));
+ }
+ }
+}
+
ngx_int_t onRequestEnd(ngx_http_request_t* r)
{
auto ctx = getOtelCtx(r);
@@ -376,9 +438,6 @@ ngx_int_t onRequestEnd(ngx_http_request_t* r)
return NGX_DECLINED;
}
- auto clcf = (ngx_http_core_loc_conf_t*)ngx_http_get_module_loc_conf(
- r, ngx_http_core_module);
-
auto now = ngx_timeofday();
auto toNanoSec = [](time_t sec, ngx_msec_t msec) -> uint64_t {
@@ -387,12 +446,13 @@ ngx_int_t onRequestEnd(ngx_http_request_t* r)
try {
BatchExporter::SpanInfo info{
- toStrView(clcf->name), ctx->current, ctx->parent.spanId,
+ getSpanName(r), ctx->current, ctx->parent.spanId,
toNanoSec(r->start_sec, r->start_msec),
toNanoSec(now->sec, now->msec)};
bool ok = gExporter->add(info, [r](BatchExporter::Span& span) {
addDefaultAttrs(span, r);
+ addCustomAttrs(span, r);
});
if (!ok) {
@@ -589,6 +649,32 @@ char* initMainConf(ngx_conf_t* cf, void* conf)
return NGX_CONF_OK;
}
+char* addSpanAttr(ngx_conf_t* cf, ngx_command_t* cmd, void* conf)
+{
+ auto lcf = (LocationConf*)conf;
+
+ if (lcf->spanAttrs.elts == NULL && ngx_array_init(&lcf->spanAttrs,
+ cf->pool, 4, sizeof(SpanAttr)) != NGX_OK) {
+ return (char*)NGX_CONF_ERROR;
+ }
+
+ auto attr = (SpanAttr*)ngx_array_push(&lcf->spanAttrs);
+ if (attr == NULL) {
+ return (char*)NGX_CONF_ERROR;
+ }
+
+ auto args = (ngx_str_t*)cf->args->elts;
+
+ attr->name = args[1];
+
+ ngx_http_compile_complex_value_t ccv = { cf, &args[2], &attr->value };
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return (char*)NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
template
ngx_int_t hexIdVar(ngx_http_request_t* r, ngx_http_variable_value_t* v,
uintptr_t data)
@@ -677,6 +763,7 @@ void* createLocationConf(ngx_conf_t* cf)
conf->trace = (ngx_http_complex_value_t*)NGX_CONF_UNSET_PTR;
conf->traceContext = NGX_CONF_UNSET_UINT;
+ conf->spanName = (ngx_http_complex_value_t*)NGX_CONF_UNSET_PTR;
return conf;
}
@@ -688,6 +775,11 @@ char* mergeLocationConf(ngx_conf_t* cf, void* parent, void* child)
ngx_conf_merge_ptr_value(conf->trace, prev->trace, NULL);
ngx_conf_merge_uint_value(conf->traceContext, prev->traceContext, 0);
+ ngx_conf_merge_ptr_value(conf->spanName, prev->spanName, NULL);
+
+ if (conf->spanAttrs.elts == NULL) {
+ conf->spanAttrs = prev->spanAttrs;
+ }
return NGX_CONF_OK;
}
diff --git a/src/str_view.hpp b/src/str_view.hpp
index debdad4..08d575f 100644
--- a/src/str_view.hpp
+++ b/src/str_view.hpp
@@ -3,3 +3,8 @@
#include
typedef opentelemetry::nostd::string_view StrView;
+
+inline bool startsWith(StrView str, StrView prefix)
+{
+ return str.substr(0, prefix.size()) == prefix;
+}