From b5c8cd8de3e5ea131681d425d0f82e378ed928c5 Mon Sep 17 00:00:00 2001
From: Pavel Pautov
Date: Sat, 9 Nov 2024 15:51:33 -0800
Subject: [PATCH] Support custom resource attributes (fix #32).
Now attributes can be set with "otel_resource_attr" directive, e.g.
otel_resource_attr my.name "my value";
---
src/batch_exporter.hpp | 11 +++--
src/http_module.cpp | 93 ++++++++++++++++++++++++++++++++----------
2 files changed, 79 insertions(+), 25 deletions(-)
diff --git a/src/batch_exporter.hpp b/src/batch_exporter.hpp
index d160d2ca..2432fe34 100644
--- a/src/batch_exporter.hpp
+++ b/src/batch_exporter.hpp
@@ -112,7 +112,8 @@ class BatchExporter {
};
BatchExporter(StrView target,
- size_t batchSize, size_t batchCount, StrView serviceName) :
+ size_t batchSize, size_t batchCount,
+ const std::map& resourceAttrs) :
batchSize(batchSize), client(std::string(target))
{
free.reserve(batchCount);
@@ -120,9 +121,11 @@ class BatchExporter {
free.emplace_back();
auto resourceSpans = free.back().add_resource_spans();
- auto attr = resourceSpans->mutable_resource()->add_attributes();
- attr->set_key("service.name");
- attr->mutable_value()->set_string_value(std::string(serviceName));
+ for (auto& attr : resourceAttrs) {
+ auto kv = resourceSpans->mutable_resource()->add_attributes();
+ kv->set_key(std::string(attr.first));
+ kv->mutable_value()->set_string_value(std::string(attr.second));
+ }
auto scopeSpans = resourceSpans->add_scope_spans();
scopeSpans->mutable_scope()->set_name("nginx");
diff --git a/src/http_module.cpp b/src/http_module.cpp
index 1ddffc30..5c73ef07 100644
--- a/src/http_module.cpp
+++ b/src/http_module.cpp
@@ -15,7 +15,7 @@ struct OtelCtx {
TraceContext current;
};
-struct MainConf {
+struct MainConfBase {
ngx_str_t endpoint;
ngx_msec_t interval;
size_t batchSize;
@@ -24,6 +24,10 @@ struct MainConf {
ngx_str_t serviceName;
};
+struct MainConf : MainConfBase {
+ std::map resourceAttrs;
+};
+
struct SpanAttr {
ngx_str_t name;
ngx_http_complex_value_t value;
@@ -38,6 +42,7 @@ struct LocationConf {
};
char* setExporter(ngx_conf_t* cf, ngx_command_t* cmd, void* conf);
+char* addResourceAttr(ngx_conf_t* cf, ngx_command_t* cmd, void* conf);
char* addSpanAttr(ngx_conf_t* cf, ngx_command_t* cmd, void* conf);
namespace Propagation {
@@ -59,14 +64,17 @@ ngx_command_t gCommands[] = {
{ ngx_string("otel_exporter"),
NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
- setExporter,
- NGX_HTTP_MAIN_CONF_OFFSET },
+ setExporter },
+
+ { ngx_string("otel_resource_attr"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE2,
+ addResourceAttr },
{ ngx_string("otel_service_name"),
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_HTTP_MAIN_CONF_OFFSET,
- offsetof(MainConf, serviceName) },
+ offsetof(MainConfBase, serviceName) },
{ ngx_string("otel_trace"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
@@ -101,25 +109,25 @@ ngx_command_t gExporterCommands[] = {
NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
0,
- offsetof(MainConf, endpoint) },
+ offsetof(MainConfBase, endpoint) },
{ ngx_string("interval"),
NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
0,
- offsetof(MainConf, interval) },
+ offsetof(MainConfBase, interval) },
{ ngx_string("batch_size"),
NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
0,
- offsetof(MainConf, batchSize) },
+ offsetof(MainConfBase, batchSize) },
{ ngx_string("batch_count"),
NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
0,
- offsetof(MainConf, batchCount) },
+ offsetof(MainConfBase, batchCount) },
ngx_null_command
};
@@ -136,6 +144,18 @@ ngx_str_t toNgxStr(StrView str)
return ngx_str_t{str.size(), (u_char*)str.data()};
}
+MainConf* getMainConf(ngx_conf_t* cf)
+{
+ return static_cast(
+ (MainConfBase*)ngx_http_conf_get_module_main_conf(cf, gHttpModule));
+}
+
+MainConf* getMainConf(ngx_cycle_t* cycle)
+{
+ return static_cast(
+ (MainConfBase*)ngx_http_cycle_get_module_main_conf(cycle, gHttpModule));
+}
+
LocationConf* getLocationConf(ngx_http_request_t* r)
{
return (LocationConf*)ngx_http_get_module_loc_conf(r, gHttpModule);
@@ -527,8 +547,7 @@ ngx_int_t initModule(ngx_conf_t* cf)
ngx_int_t initWorkerProcess(ngx_cycle_t* cycle)
{
- auto mcf = (MainConf*)ngx_http_cycle_get_module_main_conf(
- cycle, gHttpModule);
+ auto mcf = getMainConf(cycle);
// no 'http' or 'otel_exporter' blocks
if (mcf == NULL || mcf->endpoint.len == 0) {
@@ -540,7 +559,7 @@ ngx_int_t initWorkerProcess(ngx_cycle_t* cycle)
toStrView(mcf->endpoint),
mcf->batchSize,
mcf->batchCount,
- toStrView(mcf->serviceName)));
+ mcf->resourceAttrs));
} catch (const std::exception& e) {
ngx_log_error(NGX_LOG_CRIT, cycle->log, 0,
"OTel worker init error: %s", e.what());
@@ -561,8 +580,7 @@ ngx_int_t initWorkerProcess(ngx_cycle_t* cycle)
"OTel flush error: %s", e.what());
}
- auto mcf = (MainConf*)ngx_http_cycle_get_module_main_conf(
- ngx_cycle, gHttpModule);
+ auto mcf = getMainConf((ngx_cycle_t*)ngx_cycle);
ngx_add_timer(ev, mcf->interval);
};
@@ -590,7 +608,7 @@ void exitWorkerProcess(ngx_cycle_t* cycle)
char* setExporter(ngx_conf_t* cf, ngx_command_t* cmd, void* conf)
{
- auto mcf = (MainConf*)conf;
+ auto mcf = getMainConf(cf);
if (mcf->endpoint.len) {
return (char*)"is duplicate";
@@ -649,31 +667,64 @@ char* setExporter(ngx_conf_t* cf, ngx_command_t* cmd, void* conf)
return NGX_CONF_OK;
}
+char* addResourceAttr(ngx_conf_t* cf, ngx_command_t* cmd, void* conf)
+{
+ auto mcf = getMainConf(cf);
+
+ try {
+ auto args = (ngx_str_t*)cf->args->elts;
+ mcf->resourceAttrs[toStrView(args[1])] = toStrView(args[2]);
+ } catch (const std::exception& e) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "OTel: %s", e.what());
+ return (char*)NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
void* createMainConf(ngx_conf_t* cf)
{
- auto mcf = (MainConf*)ngx_pcalloc(cf->pool, sizeof(MainConf));
- if (mcf == NULL) {
+ auto cln = ngx_pool_cleanup_add(cf->pool, sizeof(MainConf));
+ if (cln == NULL) {
+ return NULL;
+ }
+
+ MainConf* mcf;
+ try {
+ mcf = new (cln->data) MainConf{};
+ } catch (const std::exception& e) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "OTel: %s", e.what());
return NULL;
}
+ cln->handler = [](void* data) {
+ ((MainConf*)data)->~MainConf();
+ };
+
mcf->interval = NGX_CONF_UNSET_MSEC;
mcf->batchSize = NGX_CONF_UNSET_SIZE;
mcf->batchCount = NGX_CONF_UNSET_SIZE;
- return mcf;
+ return static_cast(mcf);
}
char* initMainConf(ngx_conf_t* cf, void* conf)
{
auto mcf = (MainConf*)conf;
-
ngx_conf_init_msec_value(mcf->interval, 5000);
ngx_conf_init_size_value(mcf->batchSize, 512);
ngx_conf_init_size_value(mcf->batchCount, 4);
- if (mcf->serviceName.data == NULL) {
- mcf->serviceName = ngx_string("unknown_service:nginx");
+ try {
+ if (mcf->serviceName.data == NULL) {
+ mcf->resourceAttrs.emplace("service.name", "unknown_service:nginx");
+ } else {
+ mcf->resourceAttrs["service.name"] = toStrView(mcf->serviceName);
+ }
+ } catch (const std::exception& e) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "OTel: %s", e.what());
+ return (char*)NGX_CONF_ERROR;
}
return NGX_CONF_OK;
@@ -811,7 +862,7 @@ char* mergeLocationConf(ngx_conf_t* cf, void* parent, void* child)
conf->spanAttrs = prev->spanAttrs;
}
- auto mcf = (MainConf*)ngx_http_conf_get_module_main_conf(cf, gHttpModule);
+ auto mcf = getMainConf(cf);
if (mcf->endpoint.len == 0 && conf->trace) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,