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; +}