From 5d1842c5f26a5b1404787d7728e92b763af54ec1 Mon Sep 17 00:00:00 2001
From: Roy Teeuwen <c-roy.teeuwen@acolad.com>
Date: Tue, 23 Jan 2024 22:17:00 +0100
Subject: [PATCH] fixes #364: Add option to set extra resource attributes on
 the traces in Apache and Nginx

---
 .../otel-webserver-module/README.md           | 84 ++++++++++---------
 .../conf/nginx/opentelemetry_module.conf      |  1 +
 .../include/apache/ApacheConfig.h             |  7 ++
 .../include/core/api/OpentelemetrySdk.h       |  1 +
 .../include/core/api/TenantConfig.h           |  4 +
 .../opentelemetry_module.conf                 |  1 +
 .../src/apache/ApacheConfig.cpp               | 15 ++++
 .../src/apache/ApacheHooks.cpp                |  5 ++
 .../src/apache/mod_apache_otel.cpp            |  6 ++
 .../src/core/api/ApiUtils.cpp                 |  5 ++
 .../src/core/sdkwrapper/SdkHelperFactory.cpp  | 16 ++++
 .../src/nginx/ngx_http_opentelemetry_module.c | 16 ++++
 .../src/nginx/ngx_http_opentelemetry_module.h |  1 +
 13 files changed, 121 insertions(+), 41 deletions(-)

diff --git a/instrumentation/otel-webserver-module/README.md b/instrumentation/otel-webserver-module/README.md
index 8e17c46c1..0b0b4c017 100644
--- a/instrumentation/otel-webserver-module/README.md
+++ b/instrumentation/otel-webserver-module/README.md
@@ -40,25 +40,26 @@ Monitoring individual modules is crucial to the instrumentation of Apache web se
 | Apr-util                                       | 1.6.1           |
 
 ### Configuration
-| Configuration Directives                       |  Default Values |  Remarks                                   |
-| ---------------------------------------------- | --------------- | ------------------------------------------ |
-|*ApacheModuleEnabled*                           | ON              | OPTIONAL: Needed for instrumenting Apache Webserver |
-|*ApacheModuleOtelSpanExporter*                 | otlp             | OPTIONAL: Specify the span exporter to be used. Supported values are "otlp" and "ostream". All other supported values would be added in future. |
-|*ApacheModuleOtelExporterEndpoint:*             |                 | REQUIRED: The endpoint otel exporter exports to. Example "docker.for.mac.localhost:4317" |
-|*ApacheModuleOtelSpanProcessor*                 | batch           | OPTIONAL: Specify the processor to select to. Supported values are "simple" and "batch".|
-|*ApacheModuleOtelSampler*                       | AlwaysOn        | OPTIONAL: Supported values are "AlwaysOn" and "AlwaysOff" |
-|*ApacheModuleOtelMaxQueueSize*                  | 2048            | OPTIONAL: The maximum queue size. After the size is reached spans are dropped|
-|*ApacheModuleOtelScheduledDelayMillis*          | 5000            | OPTIONAL: The delay interval in milliseconds between two consecutive exports|
-|*ApacheModuleOtelExportTimeoutMillis*           | 30000           | OPTIONAL: How long the export can run in milliseconds before it is cancelled|
-|*ApacheModuleOtelMaxExportBatchSize*            | 512             | OPTIONAL: The maximum batch size of every export. It must be smaller or equal to maxQueueSize |
-|*ApacheModuleServiceName*                       |                 | REQUIRED: Logical name of the service |
-|*ApacheModuleServiceNamespace*                  |                 | REQUIRED: A namespace for the ServiceName |
-|*ApacheModuleServiceInstanceId*                 |                 | REQUIRED: The string ID of the service instance |
-|*ApacheModuleTraceAsError*                      |                 | OPTIONAL: Trace level for logging to Apache log|
-|*ApacheModuleWebserverContext*                  |                 | OPTIONAL: Takes 3 values(space-seperated) ServiceName, ServiceNamespace and ServiceInstanceId|
-|*ApacheModuleSegmentType*                       |                 | OPTIONAL: Specify the string (FIRST/LAST/CUSTOM) to be filtered for Span Name Creation|
-|*ApacheModuleSegmentParameter*                  |                 | OPTIONAL: Specify the segment count or segment numbers that you want to display for Span Creation|
-|*ApacheModuleOtelExporterHeaders*               |                 | OPTIONAL: OTEL Exporter header info or Metadata like API key for OTLP endpoint. a list of key value pairs, and these are expected to be represented in a format matching to the W3C Correlation-Context, except that additional semi-colon delimited metadata is not supported, i.e.: key1=value1,key2=value2. |
+| Configuration Directives               |  Default Values | Remarks                                                                                                                                                                                                                                                                                                       |
+|----------------------------------------| --------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| *ApacheModuleEnabled*                  | ON              | OPTIONAL: Needed for instrumenting Apache Webserver                                                                                                                                                                                                                                                           |
+| *ApacheModuleOtelSpanExporter*         | otlp             | OPTIONAL: Specify the span exporter to be used. Supported values are "otlp" and "ostream". All other supported values would be added in future.                                                                                                                                                               |
+| *ApacheModuleOtelExporterEndpoint:*    |                 | REQUIRED: The endpoint otel exporter exports to. Example "docker.for.mac.localhost:4317"                                                                                                                                                                                                                      |
+| *ApacheModuleOtelSpanProcessor*        | batch           | OPTIONAL: Specify the processor to select to. Supported values are "simple" and "batch".                                                                                                                                                                                                                      |
+| *ApacheModuleOtelSampler*              | AlwaysOn        | OPTIONAL: Supported values are "AlwaysOn" and "AlwaysOff"                                                                                                                                                                                                                                                     |
+| *ApacheModuleOtelMaxQueueSize*         | 2048            | OPTIONAL: The maximum queue size. After the size is reached spans are dropped                                                                                                                                                                                                                                 |
+| *ApacheModuleOtelScheduledDelayMillis* | 5000            | OPTIONAL: The delay interval in milliseconds between two consecutive exports                                                                                                                                                                                                                                  |
+| *ApacheModuleOtelExportTimeoutMillis*  | 30000           | OPTIONAL: How long the export can run in milliseconds before it is cancelled                                                                                                                                                                                                                                  |
+| *ApacheModuleOtelMaxExportBatchSize*   | 512             | OPTIONAL: The maximum batch size of every export. It must be smaller or equal to maxQueueSize                                                                                                                                                                                                                 |
+| *ApacheModuleServiceName*              |                 | REQUIRED: Logical name of the service                                                                                                                                                                                                                                                                         |
+| *ApacheModuleServiceNamespace*         |                 | REQUIRED: A namespace for the ServiceName                                                                                                                                                                                                                                                                     |
+| *ApacheModuleServiceInstanceId*        |                 | REQUIRED: The string ID of the service instance                                                                                                                                                                                                                                                               |
+| *ApacheModuleTraceAsError*             |                 | OPTIONAL: Trace level for logging to Apache log                                                                                                                                                                                                                                                               |
+| *ApacheModuleWebserverContext*         |                 | OPTIONAL: Takes 3 values(space-seperated) ServiceName, ServiceNamespace and ServiceInstanceId                                                                                                                                                                                                                 |
+| *ApacheModuleSegmentType*              |                 | OPTIONAL: Specify the string (FIRST/LAST/CUSTOM) to be filtered for Span Name Creation                                                                                                                                                                                                                        |
+| *ApacheModuleSegmentParameter*         |                 | OPTIONAL: Specify the segment count or segment numbers that you want to display for Span Creation                                                                                                                                                                                                             |
+| *ApacheModuleOtelExporterHeaders*      |                 | OPTIONAL: OTEL Exporter header info or Metadata like API key for OTLP endpoint. a list of key value pairs, and these are expected to be represented in a format matching to the W3C Correlation-Context, except that additional semi-colon delimited metadata is not supported, i.e.: key1=value1,key2=value2. |
+| *ApacheModuleOtelResourceAttributes*   |                 | OPTIONAL: OTEL resource attributes, a list of key value pairs,  i.e.: key1=value1,key2=value2.                                            |
 
 A sample configuration is mentioned in [opentelemetry_module.conf](https://github.com/open-telemetry/opentelemetry-cpp-contrib/blob/main/instrumentation/otel-webserver-module/opentelemetry_module.conf)
 
@@ -155,28 +156,29 @@ Currently, Nginx Webserver module monitores some fixed set of modules, which get
 | Apr-util                                       | 1.6.1           |
 
 ### Configuration
-| Configuration Directives                       |  Default Values |  Remarks                                   |
-| ---------------------------------------------- | --------------- | ------------------------------------------ |
-|*NginxModuleEnabled*                           | ON              | OPTIONAL: Needed for instrumenting Nginx Webserver |
-|*NginxModuleOtelSpanExporter*                 | otlp             | OPTIONAL: Specify the span exporter to be used. Supported values are "otlp" and "ostream". All other supported values would be added in future. |
-|*NginxModuleOtelExporterEndpoint:*             |                 | REQUIRED: The endpoint otel exporter exports to. Example "docker.for.mac.localhost:4317" |
-|*NginxModuleOtelSpanProcessor*                 | batch           | OPTIONAL: Specify the processor to select to. Supported values are "simple" and "batch".|
-|*NginxModuleOtelSampler*                       | AlwaysOn        | OPTIONAL: Supported values are "AlwaysOn" and "AlwaysOff" |
-|*NginxModuleOtelMaxQueueSize*                  | 2048            | OPTIONAL: The maximum queue size. After the size is reached spans are dropped|
-|*NginxModuleOtelScheduledDelayMillis*          | 5000            | OPTIONAL: The delay interval in milliseconds between two consecutive exports|
-|*NginxModuleOtelExportTimeoutMillis*           | 30000           | OPTIONAL: How long the export can run in milliseconds before it is cancelled|
-|*NginxModuleOtelMaxExportBatchSize*            | 512             | OPTIONAL: The maximum batch size of every export. It must be smaller or equal to maxQueueSize |
-|*NginxModuleServiceName*                       |                 | REQUIRED: Logical name of the service |
-|*NginxModuleServiceNamespace*                  |                 | REQUIRED: A namespace for the ServiceName |
-|*NginxModuleServiceInstanceId*                 |                 | REQUIRED: The string ID of the service instance |
-|*NginxModuleTraceAsError*                      |                 | OPTIONAL: Trace level for logging to Apache log|
-|*NginxModuleWebserverContext*                  |                 | OPTIONAL: Takes 3 values(space-seperated) ServiceName, ServiceNamespace and ServiceInstanceId|
-|*NginxModuleSegmentType*                       |                 | OPTIONAL: Specify the string (FIRST/LAST/CUSTOM) to be filtered for Span Name Creation|
-|*NginxModuleSegmentParameter*                  |                 | OPTIONAL: Specify the segment count or segment numbers that you want to display for Span Creation|
-|*NginxModuleRequestHeaders*                    |                 | OPTIONAL: Specify the request headers to be captured in the span attributes. The headers are Case-Sensitive and should be comma-separated. e.g.```NginxModuleRequestHeaders               Accept-Charset,Accept-Encoding,User-Agent;```|
-|*NginxModuleResponseHeaders*                   |                  | OPTIONAL: Specify the response headers to be captured in the span attributes. The headers are Case-Sensitive and should be comma-separated. e.g.```NginxModuleResponseHeaders                  Content-Length,Content-Type;```|
-|*NginxModuleOtelExporterOtlpHeaders*           |                  | OPTIONAL: OTEL exporter headers like Meta data related exposrted end point. a list of key value pairs, and these are expected to be represented in a format matching to the W3C Correlation-Context, except that additional semi-colon delimited metadata is not supported, i.e.: key1=value1,key2=value2.|
-|*NginxTrustIncomingSpans*           | ON               | OPTIONAL: Specify if you want to correlate Nginx instrumented traces and spans with incoming requests.|
+| Configuration Directives              |  Default Values | Remarks                                                                                                                                                                                                                                                                                                    |
+|---------------------------------------| --------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| *NginxModuleEnabled*                  | ON              | OPTIONAL: Needed for instrumenting Nginx Webserver                                                                                                                                                                                                                                                         |
+| *NginxModuleOtelSpanExporter*         | otlp             | OPTIONAL: Specify the span exporter to be used. Supported values are "otlp" and "ostream". All other supported values would be added in future.                                                                                                                                                            |
+| *NginxModuleOtelExporterEndpoint:*    |                 | REQUIRED: The endpoint otel exporter exports to. Example "docker.for.mac.localhost:4317"                                                                                                                                                                                                                   |
+| *NginxModuleOtelSpanProcessor*        | batch           | OPTIONAL: Specify the processor to select to. Supported values are "simple" and "batch".                                                                                                                                                                                                                   |
+| *NginxModuleOtelSampler*              | AlwaysOn        | OPTIONAL: Supported values are "AlwaysOn" and "AlwaysOff"                                                                                                                                                                                                                                                  |
+| *NginxModuleOtelMaxQueueSize*         | 2048            | OPTIONAL: The maximum queue size. After the size is reached spans are dropped                                                                                                                                                                                                                              |
+| *NginxModuleOtelScheduledDelayMillis* | 5000            | OPTIONAL: The delay interval in milliseconds between two consecutive exports                                                                                                                                                                                                                               |
+| *NginxModuleOtelExportTimeoutMillis*  | 30000           | OPTIONAL: How long the export can run in milliseconds before it is cancelled                                                                                                                                                                                                                               |
+| *NginxModuleOtelMaxExportBatchSize*   | 512             | OPTIONAL: The maximum batch size of every export. It must be smaller or equal to maxQueueSize                                                                                                                                                                                                              |
+| *NginxModuleServiceName*              |                 | REQUIRED: Logical name of the service                                                                                                                                                                                                                                                                      |
+| *NginxModuleServiceNamespace*         |                 | REQUIRED: A namespace for the ServiceName                                                                                                                                                                                                                                                                  |
+| *NginxModuleServiceInstanceId*        |                 | REQUIRED: The string ID of the service instance                                                                                                                                                                                                                                                            |
+| *NginxModuleTraceAsError*             |                 | OPTIONAL: Trace level for logging to Apache log                                                                                                                                                                                                                                                            |
+| *NginxModuleWebserverContext*         |                 | OPTIONAL: Takes 3 values(space-seperated) ServiceName, ServiceNamespace and ServiceInstanceId                                                                                                                                                                                                              |
+| *NginxModuleSegmentType*              |                 | OPTIONAL: Specify the string (FIRST/LAST/CUSTOM) to be filtered for Span Name Creation                                                                                                                                                                                                                     |
+| *NginxModuleSegmentParameter*         |                 | OPTIONAL: Specify the segment count or segment numbers that you want to display for Span Creation                                                                                                                                                                                                          |
+| *NginxModuleRequestHeaders*           |                 | OPTIONAL: Specify the request headers to be captured in the span attributes. The headers are Case-Sensitive and should be comma-separated. e.g.```NginxModuleRequestHeaders               Accept-Charset,Accept-Encoding,User-Agent;```                                                                    |
+| *NginxModuleResponseHeaders*          |                  | OPTIONAL: Specify the response headers to be captured in the span attributes. The headers are Case-Sensitive and should be comma-separated. e.g.```NginxModuleResponseHeaders                  Content-Length,Content-Type;```                                                                             |
+| *NginxModuleOtelExporterOtlpHeaders*  |                  | OPTIONAL: OTEL exporter headers like Meta data related exposrted end point. a list of key value pairs, and these are expected to be represented in a format matching to the W3C Correlation-Context, except that additional semi-colon delimited metadata is not supported, i.e.: key1=value1,key2=value2. |
+| *NginxModuleOtelResourceAttributes*   |                  | OPTIONAL: OTEL resource attributes. a list of key value pairs, i.e.: key1=value1,key2=value2.                                         |
+| *NginxTrustIncomingSpans*             | ON               | OPTIONAL: Specify if you want to correlate Nginx instrumented traces and spans with incoming requests.                                                                                                                                                                                                     |
 
 ### Build and Installation
 #### Prerequisites
diff --git a/instrumentation/otel-webserver-module/conf/nginx/opentelemetry_module.conf b/instrumentation/otel-webserver-module/conf/nginx/opentelemetry_module.conf
index e3eb6ed2d..ea735a90f 100644
--- a/instrumentation/otel-webserver-module/conf/nginx/opentelemetry_module.conf
+++ b/instrumentation/otel-webserver-module/conf/nginx/opentelemetry_module.conf
@@ -3,6 +3,7 @@ NginxModuleEnabled ON;
 NginxModuleOtelSpanExporter otlp;
 NginxModuleOtelExporterEndpoint docker.for.mac.localhost:4317;
 #NginxModuleOtelExporterOtlpHeaders Authorization=AuthorizationToken;
+NginxModuleOtelResourceAttributes some.key=value;
 # SSL Certificates
 #NginxModuleOtelSslEnabled ON
 #NginxModuleOtelSslCertificatePath 
diff --git a/instrumentation/otel-webserver-module/include/apache/ApacheConfig.h b/instrumentation/otel-webserver-module/include/apache/ApacheConfig.h
index ebef128e4..505449254 100644
--- a/instrumentation/otel-webserver-module/include/apache/ApacheConfig.h
+++ b/instrumentation/otel-webserver-module/include/apache/ApacheConfig.h
@@ -42,6 +42,9 @@ class otel_cfg
     const char* getOtelExporterOtlpHeaders() { return otelExporterOtlpHeaders; }
     int otelExporterOtlpHeadersInitialized() { return otelExporterOtlpHeaders_initialized; }
 
+    const char* getOtelResourceAttributes() { return otelResourceAttributes; }
+    int otelResourceAttributesInitialized() { return otelResourceAttributes_initialized; }
+
     int getOtelSslEnabled() { return otelSslEnabled; }
     int getOtelSslEnabledInitialized() { return otelSslEnabled_initialized; }
 
@@ -129,6 +132,9 @@ class otel_cfg
     const char *otelExporterOtlpHeaders;   // OPTIONAL: AppDynamics  Custom metadata for OTEL Exporter EX: OTEL_EXPORTER_OTLP_HEADERS="api-key=key,other-config-value=value"
     int otelExporterOtlpHeaders_initialized;
 
+    const char *otelResourceAttributes;   // OPTIONAL:  Custom resource attributes for OTEL Exporter EX: OTEL_RESOURCE_ATTRIBUTES="subsystem=key,other.value=value"
+    int otelResourceAttributes_initialized;
+
     int otelSslEnabled;      // OPTIONAL: Decision whether connection to the Exporter endpoint is secured
     int otelSslEnabled_initialized;
 
@@ -231,6 +237,7 @@ class ApacheConfigHandlers
     static const char* otel_set_otelExporterType(cmd_parms *cmd, void *conf, const char *arg);
     static const char* otel_set_otelExporterEndpoint(cmd_parms *cmd, void *conf, const char *arg);
     static const char* otel_set_otelExporterOtlpHeaders(cmd_parms *cmd, void *conf, const char *arg);
+    static const char* otel_set_otelResourceAttributes(cmd_parms *cmd, void *conf, const char *arg);
     static const char* otel_set_otelSslEnabled(cmd_parms *cmd, void *conf, const char *arg);
     static const char* otel_set_otelSslCertificatePath(cmd_parms *cmd, void *conf, const char *arg);
     static const char* otel_set_otelProcessorType(cmd_parms *cmd, void *conf, const char *arg);
diff --git a/instrumentation/otel-webserver-module/include/core/api/OpentelemetrySdk.h b/instrumentation/otel-webserver-module/include/core/api/OpentelemetrySdk.h
index b7dfec358..7ab987332 100755
--- a/instrumentation/otel-webserver-module/include/core/api/OpentelemetrySdk.h
+++ b/instrumentation/otel-webserver-module/include/core/api/OpentelemetrySdk.h
@@ -28,6 +28,7 @@
 #define OTEL_SDK_ENV_OTEL_EXPORTER_TYPE "OTEL_SDK_ENV_OTEL_EXPORTER_TYPE"
 #define OTEL_SDK_ENV_OTEL_EXPORTER_ENDPOINT "OTEL_SDK_ENV_OTEL_EXPORTER_ENDPOINT"             /*required*/
 #define OTEL_SDK_ENV_OTEL_EXPORTER_OTLPHEADERS "OTEL_SDK_ENV_OTEL_EXPORTER_OTLPHEADERS"             /*optional*/
+#define OTEL_SDK_ENV_OTEL_RESOURCE_ATTRIBUTES "OTEL_SDK_ENV_OTEL_RESOURCE_ATTRIBUTES"             /*optional*/
 #define OTEL_SDK_ENV_OTEL_SSL_ENABLED "OTEL_SDK_ENV_OTEL_SSL_ENABLED"             /*optional*/
 #define OTEL_SDK_ENV_OTEL_SSL_CERTIFICATE_PATH "OTEL_SDK_ENV_OTEL_SSL_CERTIFICATE_PATH"   /*optional*/
 #define OTEL_SDK_ENV_OTEL_PROCESSOR_TYPE "OTEL_SDK_ENV_OTEL_PROCESSOR_TYPE"
diff --git a/instrumentation/otel-webserver-module/include/core/api/TenantConfig.h b/instrumentation/otel-webserver-module/include/core/api/TenantConfig.h
index a32016e5e..cb7f62a79 100644
--- a/instrumentation/otel-webserver-module/include/core/api/TenantConfig.h
+++ b/instrumentation/otel-webserver-module/include/core/api/TenantConfig.h
@@ -46,6 +46,7 @@ class TenantConfig
     const std::string& getOtelExporterType() const {return otelExporterType;}
     const std::string& getOtelExporterEndpoint() const {return otelExporterEndpoint;}
     const std::string& getOtelExporterOtlpHeaders() const {return otelExporterOtlpHeaders;}
+    const std::string& getOtelResourceAttributes() const {return otelResourceAttributes;}
     const std::string& getOtelProcessorType() const {return otelProcessorType;}
     const unsigned getOtelMaxQueueSize() const {return otelMaxQueueSize;}
     const unsigned getOtelScheduledDelayMillis() const {return otelScheduledDelayMillis;}
@@ -63,6 +64,7 @@ class TenantConfig
     void setOtelExporterType(const std::string& otelExporterType) { this->otelExporterType = otelExporterType; }
     void setOtelExporterEndpoint(const std::string& otelExporterEndpoint) { this->otelExporterEndpoint = otelExporterEndpoint; }
     void setOtelExporterOtlpHeaders(const std::string& otelExporterOtlpHeaders) { this->otelExporterOtlpHeaders = otelExporterOtlpHeaders; }
+    void setOtelResourceAttributes(const std::string& otelResourceAttributes) { this->otelResourceAttributes = otelResourceAttributes; }
     void setOtelProcessorType(const std::string& otelProcessorType) { this->otelProcessorType = otelProcessorType; }
     void setOtelMaxQueueSize(const unsigned int otelMaxQueueSize) { this->otelMaxQueueSize = otelMaxQueueSize; }
     void setOtelScheduledDelayMillis(const unsigned int otelScheduledDelayMillis) { this->otelScheduledDelayMillis = otelScheduledDelayMillis; }
@@ -83,6 +85,7 @@ class TenantConfig
     std::string otelExporterType;
     std::string otelExporterEndpoint;
     std::string otelExporterOtlpHeaders;
+    std::string otelResourceAttributes;
     bool otelSslEnabled;
     std::string otelSslCertPath;
 
@@ -112,6 +115,7 @@ inline std::ostream& operator<< (std::ostream &os, const otel::core::TenantConfi
         << "\n OtelSslEnabled                   " << config.getOtelSslEnabled()
         << "\n OtelSslCertPath                  " << config.getOtelSslCertPath()
         << "\n OtelExportOtlpHeaders            " << config.getOtelExporterOtlpHeaders()
+        << "\n OtelResourceAttributes           " << config.getOtelResourceAttributes()
         << "";
     return os;
 }
diff --git a/instrumentation/otel-webserver-module/opentelemetry_module.conf b/instrumentation/otel-webserver-module/opentelemetry_module.conf
index b5b0a9194..7a39300e2 100755
--- a/instrumentation/otel-webserver-module/opentelemetry_module.conf
+++ b/instrumentation/otel-webserver-module/opentelemetry_module.conf
@@ -15,6 +15,7 @@ ApacheModuleEnabled ON
 ApacheModuleOtelSpanExporter otlp
 ApacheModuleOtelExporterEndpoint collector:4317
 #ApacheModuleOtelExporterHeaders api-key=abc123
+#ApacheModuleOtelResourceAttributes some.key=value
 
 # SSL Certificates
 #ApacheModuleOtelSslEnabled ON
diff --git a/instrumentation/otel-webserver-module/src/apache/ApacheConfig.cpp b/instrumentation/otel-webserver-module/src/apache/ApacheConfig.cpp
index 92c59e875..f4cf33461 100644
--- a/instrumentation/otel-webserver-module/src/apache/ApacheConfig.cpp
+++ b/instrumentation/otel-webserver-module/src/apache/ApacheConfig.cpp
@@ -129,6 +129,14 @@ const char* ApacheConfigHandlers::otel_set_otelExporterOtlpHeaders(cmd_parms *cm
     return helperChar(cmd, cfg, arg, cfg->otelExporterOtlpHeaders, cfg->otelExporterOtlpHeaders_initialized, "otel_set_otelExporterOtlpHeaders");
 }
 
+//  char *otelResourceAttributes;
+//  int otelResourceAttributes_initialized;
+const char* ApacheConfigHandlers::otel_set_otelResourceAttributes(cmd_parms *cmd, void *conf, const char *arg)
+{
+    otel_cfg* cfg = (otel_cfg*) conf;
+    return helperChar(cmd, cfg, arg, cfg->otelResourceAttributes, cfg->otelResourceAttributes_initialized, "otel_set_otelResourceAttributes");
+}
+
 //  char *otelSslEnabled;
 //  int otelSslEnabled_initialized;
 const char* ApacheConfigHandlers::otel_set_otelSslEnabled(cmd_parms *cmd, void *conf, const char *arg)
@@ -444,6 +452,10 @@ void otel_cfg::init()
     otelExporterOtlpHeaders = "";
     otelExporterOtlpHeaders_initialized = 0;
 
+    // otelResourceAttributes         Optional: OTLP resource attributes as key value pairs
+    otelResourceAttributes = "";
+    otelResourceAttributes_initialized = 0;
+
     // otelSslEnabled       OPTIONAL: Decides whether the connection to the endpoint is secured
     otelSslEnabled = 0;
     otelSslEnabled_initialized = 0;
@@ -786,6 +798,9 @@ otel_cfg* ApacheConfigHandlers::getProcessConfig(const request_rec* r)
     process_cfg->otelExporterOtlpHeaders = apr_pstrdup(r->server->process->pool, our_config->otelExporterOtlpHeaders);
     process_cfg->otelExporterOtlpHeaders_initialized = our_config->otelExporterOtlpHeaders_initialized;
 
+    process_cfg->otelResourceAttributes = apr_pstrdup(r->server->process->pool, our_config->otelResourceAttributes);
+    process_cfg->otelResourceAttributes_initialized = our_config->otelResourceAttributes_initialized;
+
     process_cfg->otelSslEnabled = our_config->otelSslEnabled;
     process_cfg->otelSslEnabled_initialized = our_config->otelSslEnabled_initialized;
 
diff --git a/instrumentation/otel-webserver-module/src/apache/ApacheHooks.cpp b/instrumentation/otel-webserver-module/src/apache/ApacheHooks.cpp
index 1bb575575..8257016c7 100644
--- a/instrumentation/otel-webserver-module/src/apache/ApacheHooks.cpp
+++ b/instrumentation/otel-webserver-module/src/apache/ApacheHooks.cpp
@@ -431,6 +431,11 @@ bool ApacheHooks::initialize_opentelemetry(const request_rec *r)
         env_config[ix].value = our_config->getOtelExporterOtlpHeaders();
         ++ix;
 
+        // Resource attributes
+        env_config[ix].name = OTEL_SDK_ENV_OTEL_RESOURCE_ATTRIBUTES;
+        env_config[ix].value = our_config->getOtelResourceAttributes();
+        ++ix;
+
         // !!!
         // Remember to update the apr_pcalloc call size if we add another parameter to the input array!
         // !!!
diff --git a/instrumentation/otel-webserver-module/src/apache/mod_apache_otel.cpp b/instrumentation/otel-webserver-module/src/apache/mod_apache_otel.cpp
index f01fdd550..82c0a8aeb 100644
--- a/instrumentation/otel-webserver-module/src/apache/mod_apache_otel.cpp
+++ b/instrumentation/otel-webserver-module/src/apache/mod_apache_otel.cpp
@@ -70,6 +70,12 @@ static const command_rec otel_cmds[] =
             NULL,
             OR_ALL,
             "AppDynamics Otel export Headers key value pairs"),
+    AP_INIT_TAKE1(
+            "apacheModuleOtelResourceAttributes",
+            (CMD_HAND_TYPE)ApacheConfigHandlers::otel_set_otelResourceAttributes,
+            NULL,
+            OR_ALL,
+            "Otel resource attributes key value pairs"),
     AP_INIT_TAKE1(
             "apacheModuleOtelSslEnabled",
             (CMD_HAND_TYPE)ApacheConfigHandlers::otel_set_otelSslEnabled,
diff --git a/instrumentation/otel-webserver-module/src/core/api/ApiUtils.cpp b/instrumentation/otel-webserver-module/src/core/api/ApiUtils.cpp
index 1a2931e06..c22b0bd70 100755
--- a/instrumentation/otel-webserver-module/src/core/api/ApiUtils.cpp
+++ b/instrumentation/otel-webserver-module/src/core/api/ApiUtils.cpp
@@ -168,6 +168,7 @@ OTEL_SDK_STATUS_CODE ApiUtils::ReadSettingsFromReader(
     std::string otelExporterType;
     std::string otelExporterEndpoint;
     std::string otelExporterOtlpHeaders;
+    std::string otelResourceAttributes;
     bool otelSslEnabled;
     std::string otelSslCertPath;
     std::string otelLibraryName;
@@ -259,6 +260,9 @@ OTEL_SDK_STATUS_CODE ApiUtils::ReadSettingsFromReader(
     reader.ReadOptional(
             std::string(OTEL_SDK_ENV_OTEL_EXPORTER_OTLPHEADERS), otelExporterOtlpHeaders);
 
+    reader.ReadOptional(
+            std::string(OTEL_SDK_ENV_OTEL_RESOURCE_ATTRIBUTES), otelResourceAttributes);
+
 
     tenantConfig.setServiceNamespace(serviceNamespace);
     tenantConfig.setServiceName(serviceName);
@@ -266,6 +270,7 @@ OTEL_SDK_STATUS_CODE ApiUtils::ReadSettingsFromReader(
     tenantConfig.setOtelExporterType(otelExporterType);
     tenantConfig.setOtelExporterEndpoint(otelExporterEndpoint);
     tenantConfig.setOtelExporterOtlpHeaders(otelExporterOtlpHeaders);
+    tenantConfig.setOtelResourceAttributes(otelResourceAttributes);
     tenantConfig.setOtelLibraryName(otelLibraryName);
     tenantConfig.setOtelProcessorType(otelProcessorType);
     tenantConfig.setOtelSamplerType(otelSamplerType);
diff --git a/instrumentation/otel-webserver-module/src/core/sdkwrapper/SdkHelperFactory.cpp b/instrumentation/otel-webserver-module/src/core/sdkwrapper/SdkHelperFactory.cpp
index 0082276c3..84f8b02eb 100644
--- a/instrumentation/otel-webserver-module/src/core/sdkwrapper/SdkHelperFactory.cpp
+++ b/instrumentation/otel-webserver-module/src/core/sdkwrapper/SdkHelperFactory.cpp
@@ -65,6 +65,22 @@ SdkHelperFactory::SdkHelperFactory(
     attributes[kServiceNamespace] = config->getServiceNamespace();
     attributes[kServiceInstanceId] = config->getServiceInstanceId();
 
+    opentelemetry::common::KeyValueStringTokenizer tokenizer{config->getOtelResourceAttributes()};
+    opentelemetry::nostd::string_view resource_key;
+    opentelemetry::nostd::string_view resource_value;
+    bool resource_valid = true;
+
+    while (tokenizer.next(resource_valid, resource_key, resource_value))
+    {
+        if (resource_valid)
+        {
+            std::string key = static_cast<std::string>(resource_key);
+            std::string value = static_cast<std::string>(resource_value);
+            attributes[key] = value;
+        }
+    }
+
+
     // NOTE : resource attribute values are nostd::variant and so we need to explicitely set it to std::string
     std::string libraryVersion = MODULE_VERSION;
     std::string cppSDKVersion = CPP_SDK_VERSION;
diff --git a/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.c b/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.c
index 8e4d5005b..fe209b7b4 100644
--- a/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.c
+++ b/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.c
@@ -209,6 +209,13 @@ static ngx_command_t ngx_http_opentelemetry_commands[] = {
       offsetof(ngx_http_opentelemetry_loc_conf_t, nginxModuleOtelExporterOtlpHeaders),
       NULL},
 
+    { ngx_string("NginxModuleOtelResourceAttributes"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_opentelemetry_loc_conf_t, nginxModuleOtelResourceAttributes),
+      NULL},
+
     { ngx_string("NginxModuleOtelSpanProcessor"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
       ngx_conf_set_str_slot,
@@ -467,6 +474,7 @@ static char* ngx_http_opentelemetry_merge_loc_conf(ngx_conf_t *cf, void *parent,
     ngx_conf_merge_str_value(conf->nginxModuleOtelSpanExporter, prev->nginxModuleOtelSpanExporter, "");
     ngx_conf_merge_str_value(conf->nginxModuleOtelExporterEndpoint, prev->nginxModuleOtelExporterEndpoint, "");
     ngx_conf_merge_str_value(conf->nginxModuleOtelExporterOtlpHeaders, prev->nginxModuleOtelExporterOtlpHeaders, "");
+    ngx_conf_merge_str_value(conf->nginxModuleOtelResourceAttributes, prev->nginxModuleOtelResourceAttributes, "");
     ngx_conf_merge_value(conf->nginxModuleOtelSslEnabled, prev->nginxModuleOtelSslEnabled, 0);
     ngx_conf_merge_str_value(conf->nginxModuleOtelSslCertificatePath, prev->nginxModuleOtelSslCertificatePath, "");
     ngx_conf_merge_str_value(conf->nginxModuleOtelSpanProcessor, prev->nginxModuleOtelSpanProcessor, "");
@@ -959,6 +967,13 @@ static ngx_flag_t ngx_initialize_opentelemetry(ngx_http_request_t *r)
         env_config[ix].value = (const char*)(conf->nginxModuleOtelExporterOtlpHeaders).data;
         ++ix;
 
+        // Otel Exporter Resource Attributes
+        env_config[ix].name = OTEL_SDK_ENV_OTEL_RESOURCE_ATTRIBUTES;
+        env_config[ix].value = (const char*)(conf->nginxModuleOtelResourceAttributes).data;
+        ++ix;
+
+        // TODO should resource attributes be added here?
+
         // Otel SSL Enabled
         env_config[ix].name = OTEL_SDK_ENV_OTEL_SSL_ENABLED;
         env_config[ix].value = conf->nginxModuleOtelSslEnabled == 1 ? "1" : "0";
@@ -1493,6 +1508,7 @@ static void traceConfig(ngx_http_request_t *r, ngx_http_opentelemetry_loc_conf_t
                                                       conf->nginxModuleEnabled,
                                                       (conf->nginxModuleOtelExporterEndpoint).data,
                                                       (conf->nginxModuleOtelExporterOtlpHeaders).data,
+                                                      (conf->nginxModuleOtelResourceAttributes).data,
                                                       conf->nginxModuleOtelSslEnabled,
                                                       (conf->nginxModuleOtelSslCertificatePath).data,
                                                       (conf->nginxModuleOtelSpanExporter).data,
diff --git a/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.h b/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.h
index 6043b5b78..cea022431 100644
--- a/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.h
+++ b/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.h
@@ -103,6 +103,7 @@ typedef struct {
     ngx_str_t   nginxModuleRequestHeaders;
     ngx_str_t   nginxModuleResponseHeaders;
     ngx_str_t   nginxModuleOtelExporterOtlpHeaders;
+    ngx_str_t   nginxModuleOtelResourceAttributes;
     ngx_flag_t  nginxTrustIncomingSpans;
 
 } ngx_http_opentelemetry_loc_conf_t;