From 08f98e95277b27b610100deeb754b0699ea1df49 Mon Sep 17 00:00:00 2001 From: Brian Cullen Date: Tue, 28 Jan 2025 12:57:19 +0100 Subject: [PATCH] Add Additional Resource Attributes for OpenTelemetry Adds the "deployment.environment" resource attribute to those that can be configured for OpenTelemetry. This was done as some collectors, including Datadog, require this value to properly classify traces. Note: the "deployment.environment" attribute is being deprecated in future versions of the OTel schemas and this may need to be updated when that library is upgraded. Fixes #7322 Signed-off-by: Brian Cullen --- docs/content/configuration.md | 30 +++++++------- .../distributedtracing/distributedtracing.go | 14 +++++-- v1/server/server_test.go | 39 +++++++++++++++---- 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/docs/content/configuration.md b/docs/content/configuration.md index ad3bb12615..7f295fa36e 100644 --- a/docs/content/configuration.md +++ b/docs/content/configuration.md @@ -88,6 +88,7 @@ distributed_tracing: service_namespace: "my-namespace" service_version: "1.1" service_instance_id: "1" + deployment_environment: "prod" server: decoding: @@ -876,20 +877,21 @@ respectively, and the `json.schema_match` built-in function for compiled JSON sc Distributed tracing represents the configuration of the OpenTelemetry Tracing. -| Field | Type | Required | Description | -|----------------------------------------------------|----------| --- |------------------------------------------------------------------------------------| -| `distributed_tracing.type` | `string` | No | Setting this to "grpc" enables distributed tracing with an collector gRPC endpoint | -| `distributed_tracing.address` | `string` | No (default: `localhost:4317`) | Address of the OpenTelemetry Collector gRPC endpoint. | -| `distributed_tracing.service_name` | `string` | No (default: `opa`) | Logical name of the service. | -| `distributed_tracing.sample_percentage` | `int` | No (default: `100`) | Percentage of traces that are sampled and exported. | -| `distributed_tracing.encryption` | `string` | No (default: `off`) | Configures TLS. | -| `distributed_tracing.allow_insecure_tls` | `bool` | No (default: `false`) | Allow insecure TLS. | -| `distributed_tracing.tls_ca_cert_file` | `string` | No | The path to the root CA certificate. | -| `distributed_tracing.tls_cert_file` | `string` | No (unless `encryption` equals `mtls`) | The path to the client certificate to authenticate with. | -| `distributed_tracing.tls_private_key_file` | `string` | No (unless `tls_cert_file` provided) | The path to the private key of the client certificate. | -| `distributed_tracing.resource.service_version` | `string` | No | Service version | -| `distributed_tracing.resource.service_instance_id` | `string` | No | Service instance id | -| `distributed_tracing.resource.service_namespace` | `string` | No | Service namespace | +| Field | Type | Required | Description | +|-------------------------------------------------------|----------|----------------------------------------|------------------------------------------------------------------------------------| +| `distributed_tracing.type` | `string` | No | Setting this to "grpc" enables distributed tracing with an collector gRPC endpoint | +| `distributed_tracing.address` | `string` | No (default: `localhost:4317`) | Address of the OpenTelemetry Collector gRPC endpoint. | +| `distributed_tracing.service_name` | `string` | No (default: `opa`) | Logical name of the service. | +| `distributed_tracing.sample_percentage` | `int` | No (default: `100`) | Percentage of traces that are sampled and exported. | +| `distributed_tracing.encryption` | `string` | No (default: `off`) | Configures TLS. | +| `distributed_tracing.allow_insecure_tls` | `bool` | No (default: `false`) | Allow insecure TLS. | +| `distributed_tracing.tls_ca_cert_file` | `string` | No | The path to the root CA certificate. | +| `distributed_tracing.tls_cert_file` | `string` | No (unless `encryption` equals `mtls`) | The path to the client certificate to authenticate with. | +| `distributed_tracing.tls_private_key_file` | `string` | No (unless `tls_cert_file` provided) | The path to the private key of the client certificate. | +| `distributed_tracing.resource.service_version` | `string` | No | Service version | +| `distributed_tracing.resource.service_instance_id` | `string` | No | Service instance id | +| `distributed_tracing.resource.service_namespace` | `string` | No | Service namespace | +| `distributed_tracing.resource.deployment_environment` | `string` | No | Deployment environment name | The following encryption methods are supported: diff --git a/internal/distributedtracing/distributedtracing.go b/internal/distributedtracing/distributedtracing.go index ec047cc297..3fe11d3c13 100644 --- a/internal/distributedtracing/distributedtracing.go +++ b/internal/distributedtracing/distributedtracing.go @@ -55,9 +55,10 @@ func isSupportedSampleRatePercentage(sampleRate int) bool { } type resourceConfig struct { - ServiceVersion string `json:"service_version,omitempty"` - ServiceInstanceID string `json:"service_instance_id,omitempty"` - ServiceNamespace string `json:"service_namespace,omitempty"` + ServiceVersion string `json:"service_version,omitempty"` + ServiceInstanceID string `json:"service_instance_id,omitempty"` + ServiceNamespace string `json:"service_namespace,omitempty"` + DeploymentEnvironment string `json:"deployment_environment,omitempty"` } type distributedTracingConfig struct { @@ -117,6 +118,13 @@ func Init(ctx context.Context, raw []byte, id string) (*otlptrace.Exporter, *tra if distributedTracingConfig.Resource.ServiceNamespace != "" { resourceAttributes = append(resourceAttributes, semconv.ServiceNamespaceKey.String(distributedTracingConfig.Resource.ServiceNamespace)) } + + // NOTE: this is currently using the `deployment.environment` setting which is being deprecated + // in favour of `deployment.environment.name` in future versions of the OpenTelemetry schema. + // This will need to be taken into account when upgrading the library version in the future. + if distributedTracingConfig.Resource.DeploymentEnvironment != "" { + resourceAttributes = append(resourceAttributes, semconv.DeploymentEnvironmentKey.String(distributedTracingConfig.Resource.DeploymentEnvironment)) + } res, err := resource.New(ctx, resource.WithAttributes( semconv.ServiceNameKey.String(distributedTracingConfig.ServiceName), diff --git a/v1/server/server_test.go b/v1/server/server_test.go index 3882a6baf2..752177e5ba 100644 --- a/v1/server/server_test.go +++ b/v1/server/server_test.go @@ -37,6 +37,9 @@ import ( "testing" "time" + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.7.0" + "github.com/gorilla/mux" "github.com/open-policy-agent/opa/internal/distributedtracing" @@ -5857,15 +5860,28 @@ func TestDistributedTracingEnabled(t *testing.T) { func TestDistributedTracingResourceAttributes(t *testing.T) { t.Parallel() - c := []byte(`{"distributed_tracing": { + attributes := map[attribute.Key]string{ + semconv.DeploymentEnvironmentKey: "prod", + semconv.ServiceNameKey: "my-service", + semconv.ServiceVersionKey: "1.0", + semconv.ServiceNamespaceKey: "my-namespace", + semconv.ServiceInstanceIDKey: "1", + } + + c := []byte(fmt.Sprintf(`{"distributed_tracing": { "type": "grpc", - "service_name": "my-service", + "service_name": "%s", "resource": { - "service_namespace": "my-namespace", - "service_version": "1.0", - "service_instance_id": "1" + "service_namespace": "%s", + "service_version": "%s", + "service_instance_id": "%s", + "deployment_environment": "%s" } - }}`) + }}`, attributes[semconv.ServiceNameKey], + attributes[semconv.ServiceNamespaceKey], + attributes[semconv.ServiceVersionKey], + attributes[semconv.ServiceInstanceIDKey], + attributes[semconv.DeploymentEnvironmentKey])) ctx := context.Background() _, traceProvider, resource, err := distributedtracing.Init(ctx, c, "foo") @@ -5878,9 +5894,16 @@ func TestDistributedTracingResourceAttributes(t *testing.T) { if resource == nil { t.Fatalf("Resource was not initialized") } - if len(resource.Attributes()) != 4 { - t.Fatalf("Unexpected resource attributes count. Expected: %v, Got: %v", 4, len(resource.Attributes())) + if len(resource.Attributes()) != 5 { + t.Fatalf("Unexpected resource attributes count. Expected: %v, Got: %v", 5, len(resource.Attributes())) } + + for _, value := range resource.Attributes() { + if attribute.StringValue(attributes[value.Key]) != value.Value { + t.Fatalf("Unexpected resource attribute. Expected: %v, Got: %v", attributes[value.Key], value) + } + } + } func TestCertPoolReloading(t *testing.T) {