diff --git a/instrumentation/nginx/README.md b/instrumentation/nginx/README.md index 3ba37d761..f1379f600 100644 --- a/instrumentation/nginx/README.md +++ b/instrumentation/nginx/README.md @@ -40,7 +40,9 @@ load_module /path/to/otel_ngx_module.so; http { opentelemetry_service_name "nginx-proxy"; - opentelemetry_otlp_traces_endpoint "http://collector:4318/v1/traces" + opentelemetry_otlp_traces_endpoint "http://collector:4318/v1/traces"; + opentelemetry_resource_attr "service.version" "1.0.0"; + opentelemetry_resource_attr "deployment.environment.name" "production"; server { listen 80; @@ -104,6 +106,14 @@ Service name for the nginx instance (default: `uknown:nginx`). - **syntax**: `opentelemetry_service_name ` - **block**: `http` +### `opentelemetry_resource_attr` + +Adds a custom resource attribute to the trace provider. Resource attributes represent the entity producing telemetry data, e.g. `opentelemetry_resource_attr "service.version" "1.0.0"` or `opentelemetry_resource_attr "deployment.environment.name" "production"`. + +- **required**: `false` +- **syntax**: `opentelemetry_resource_attr ` +- **block**: `http` + ### `opentelemetry_span_processor` Chooses between simple and batch span processor (default: `batch`). diff --git a/instrumentation/nginx/src/agent_config.h b/instrumentation/nginx/src/agent_config.h index 2e667c5dd..ea99bc72f 100644 --- a/instrumentation/nginx/src/agent_config.h +++ b/instrumentation/nginx/src/agent_config.h @@ -1,6 +1,7 @@ #pragma once #include +#include extern "C" { #include @@ -38,4 +39,5 @@ struct OtelNgxAgentConfig std::string sampler = "parentbased_always_on"; double samplerRatio = 1.0; + std::unordered_map resourceAttributes; }; diff --git a/instrumentation/nginx/src/otel_ngx_module.cpp b/instrumentation/nginx/src/otel_ngx_module.cpp index 0aacebe52..f32c615b0 100644 --- a/instrumentation/nginx/src/otel_ngx_module.cpp +++ b/instrumentation/nginx/src/otel_ngx_module.cpp @@ -1051,6 +1051,41 @@ char* OtelNgxSetTracesSamplerRatio(ngx_conf_t* cf, ngx_command_t*, void*) { return NGX_CONF_OK; } +char* OtelNgxSetResourceAttr(ngx_conf_t* cf, ngx_command_t*, void*) { + OtelMainConf* otelMainConf = GetOtelMainConf(cf); + + ngx_str_t* values = (ngx_str_t*)cf->args->elts; + + if (cf->args->nelts != 3) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "opentelemetry_resource_attr takes 2 arguments"); + return (char*)NGX_CONF_ERROR; + } + + ngx_str_t* key = &values[1]; + ngx_str_t* value = &values[2]; + + if (key->len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "opentelemetry_resource_attr key cannot be empty"); + return (char*)NGX_CONF_ERROR; + } + + if (value->len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "opentelemetry_resource_attr value cannot be empty"); + return (char*)NGX_CONF_ERROR; + } + + // Safe string construction with explicit length + std::string strKey((const char*)key->data, key->len); + std::string strValue((const char*)value->data, value->len); + + otelMainConf->agentConfig.resourceAttributes[strKey] = strValue; + + return NGX_CONF_OK; +} + static char* OtelNgxSetCustomAttribute(ngx_conf_t* conf, ngx_command_t*, void* userConf) { OtelNgxLocationConf* locConf = (OtelNgxLocationConf*)userConf; @@ -1250,6 +1285,14 @@ static ngx_command_t kOtelNgxCommands[] = { 0, nullptr, }, + { + ngx_string("opentelemetry_resource_attr"), + NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE2, + OtelNgxSetResourceAttr, + NGX_HTTP_MAIN_CONF_OFFSET, + 0, + nullptr, + }, #if (NGX_PCRE) { ngx_string("opentelemetry_sensitive_header_names"), @@ -1372,10 +1415,22 @@ static ngx_int_t OtelNgxStart(ngx_cycle_t* cycle) { } auto processor = CreateProcessor(agentConf, std::move(exporter)); + + // Build resource attributes + opentelemetry::sdk::resource::ResourceAttributes resourceAttrs; + for (const auto& attr : agentConf->resourceAttributes) { + resourceAttrs[attr.first] = attr.second; + } + + // Set service.name if not already provided via resource attributes + if (resourceAttrs.find("service.name") == resourceAttrs.end()) { + resourceAttrs["service.name"] = serviceName; + } + auto provider = nostd::shared_ptr(new sdktrace::TracerProvider( std::move(processor), - opentelemetry::sdk::resource::Resource::Create({{"service.name", serviceName}}), + opentelemetry::sdk::resource::Resource::Create(resourceAttrs), std::move(sampler))); opentelemetry::trace::Provider::SetTracerProvider(std::move(provider)); diff --git a/instrumentation/nginx/test/conf/nginx.conf b/instrumentation/nginx/test/conf/nginx.conf index 4798d79fe..06abe3fc7 100644 --- a/instrumentation/nginx/test/conf/nginx.conf +++ b/instrumentation/nginx/test/conf/nginx.conf @@ -7,6 +7,8 @@ http { opentelemetry_service_name "nginx-proxy"; opentelemetry_otlp_traces_endpoint "http://collector:4318/v1/traces"; opentelemetry_span_processor "simple"; + opentelemetry_resource_attr "service.version" "1.0.0"; + opentelemetry_resource_attr "deployment.environment.name" "test"; opentelemetry_operation_name otel_test; opentelemetry_ignore_paths ignored.php; access_log stderr; diff --git a/instrumentation/nginx/test/instrumentation/test/instrumentation_test.exs b/instrumentation/nginx/test/instrumentation/test/instrumentation_test.exs index f8569aac1..a31117417 100644 --- a/instrumentation/nginx/test/instrumentation/test/instrumentation_test.exs +++ b/instrumentation/nginx/test/instrumentation/test/instrumentation_test.exs @@ -154,6 +154,8 @@ defmodule InstrumentationTest do assert status == 200 assert attrib(resource, "service.name") == "nginx-proxy" + assert attrib(resource, "service.version") == "1.0.0" + assert attrib(resource, "deployment.environment.name") == "test" end test "HTTP upstream | span attributes", %{trace_file: trace_file} do