From 32e174141436408024f1319695806ab1cf721369 Mon Sep 17 00:00:00 2001 From: khushiiagrawal Date: Wed, 28 Jan 2026 18:04:19 +0530 Subject: [PATCH 1/4] fix(eventing): suppress verbose OTEL logging in EventTransform Signed-off-by: khushiiagrawal --- .../eventtransform/resources_jsonata.go | 79 +++++++++++++++++-- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/pkg/reconciler/eventtransform/resources_jsonata.go b/pkg/reconciler/eventtransform/resources_jsonata.go index a5b4ce9c79d..c8dd0b703d9 100644 --- a/pkg/reconciler/eventtransform/resources_jsonata.go +++ b/pkg/reconciler/eventtransform/resources_jsonata.go @@ -60,6 +60,14 @@ const ( JsonataTLSVolumePath = "/etc/jsonata-tls" JsonataTLSKeyPath = JsonataTLSVolumePath + "/" + eventingtls.TLSKey JsonataTLSCertPath = JsonataTLSVolumePath + "/" + eventingtls.TLSCrt + + // OTEL environment variable names for tracing auto-configuration + // These are standard OpenTelemetry environment variables that the Node.js SDK + // automatically reads to configure the trace exporter. + OTELExporterEndpointEnv = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT" + OTELExporterProtocolEnv = "OTEL_EXPORTER_OTLP_PROTOCOL" + OTELTracesSamplerEnv = "OTEL_TRACES_SAMPLER" + OTELTracesSamplerArgEnv = "OTEL_TRACES_SAMPLER_ARG" ) func jsonataExpressionConfigMap(_ context.Context, transform *eventing.EventTransform) corev1.ConfigMap { @@ -121,13 +129,16 @@ func jsonataDeployment(ctx context.Context, withCombinedTrustBundle bool, cw *re Name: "jsonata-event-transform", Image: image, Env: append( - []corev1.EnvVar{ - { - Name: "JSONATA_TRANSFORM_FILE_NAME", - Value: filepath.Join(JsonataExpressionPath, JsonataExpressionDataKey), + append( + []corev1.EnvVar{ + { + Name: "JSONATA_TRANSFORM_FILE_NAME", + Value: filepath.Join(JsonataExpressionPath, JsonataExpressionDataKey), + }, }, - }, - cw.ToEnvVars()..., + cw.ToEnvVars()..., + ), + otelTracingEnvVars(cw)..., ), VolumeMounts: []corev1.VolumeMount{ { @@ -460,3 +471,59 @@ func jsonataCertificate(ctx context.Context, transform *eventing.EventTransform) ), ) } + +// otelTracingEnvVars generates standard OpenTelemetry environment variables +// based on the observability configuration. This allows the Node.js OTEL SDK +// in the transform-jsonata container to auto-configure the trace exporter +// and send traces to the configured endpoint instead of logging to stdout. +func otelTracingEnvVars(cw *reconcilersource.ConfigWatcher) []corev1.EnvVar { + if cw == nil { + return nil + } + + obsCfg := cw.ObservabilityConfig() + if obsCfg == nil { + return nil + } + + tracingCfg := obsCfg.Tracing + // Only add OTEL env vars if tracing is enabled with a real exporter + if tracingCfg.Protocol == "" || tracingCfg.Protocol == "none" { + return nil + } + + // For stdout protocol, don't set OTEL env vars - let the app use ConsoleSpanExporter + if tracingCfg.Protocol == "stdout" { + return nil + } + + envVars := []corev1.EnvVar{ + { + Name: OTELExporterProtocolEnv, + Value: tracingCfg.Protocol, + }, + } + + if tracingCfg.Endpoint != "" { + envVars = append(envVars, corev1.EnvVar{ + Name: OTELExporterEndpointEnv, + Value: tracingCfg.Endpoint, + }) + } + + // Configure sampling if a sampling rate is set + if tracingCfg.SamplingRate > 0 { + envVars = append(envVars, + corev1.EnvVar{ + Name: OTELTracesSamplerEnv, + Value: "parentbased_traceidratio", + }, + corev1.EnvVar{ + Name: OTELTracesSamplerArgEnv, + Value: fmt.Sprintf("%g", tracingCfg.SamplingRate), + }, + ) + } + + return envVars +} From 6b7522bc932b5197db41476c0b8558b7ef1f7fc9 Mon Sep 17 00:00:00 2001 From: khushiiagrawal Date: Fri, 13 Feb 2026 18:45:25 +0530 Subject: [PATCH 2/4] Implement tests for OpenTelemetry tracing environment variables in Jsonata deployment Signed-off-by: khushiiagrawal --- .../eventtransform/eventtransform_test.go | 258 ++++++++++++++++++ .../eventtransform/resources_jsonata.go | 45 ++- 2 files changed, 288 insertions(+), 15 deletions(-) diff --git a/pkg/reconciler/eventtransform/eventtransform_test.go b/pkg/reconciler/eventtransform/eventtransform_test.go index 0542c02c982..7f95d6c412e 100644 --- a/pkg/reconciler/eventtransform/eventtransform_test.go +++ b/pkg/reconciler/eventtransform/eventtransform_test.go @@ -2208,3 +2208,261 @@ func eventJsonataCertificateUpdated() string { func eventJsonataCertificateDeleted() string { return Eventf(corev1.EventTypeNormal, "JsonataCertificateDeleted", fmt.Sprintf("%s-%s", testName, "jsonata")) } + +func TestOtelTracingEnvVars(t *testing.T) { + ctx := context.Background() + logger := logtesting.TestLogger(t) + ctx = logging.WithLogger(ctx, logger) + + tests := []struct { + name string + cwFactory func() *reconcilersource.ConfigWatcher + wantEnvVars []corev1.EnvVar + }{ + { + name: "nil config watcher returns nil", + cwFactory: func() *reconcilersource.ConfigWatcher { return nil }, + wantEnvVars: nil, + }, + { + name: "empty observability config returns nil", + cwFactory: func() *reconcilersource.ConfigWatcher { + return reconcilersource.WatchConfigurations(ctx, "test", + configmap.NewStaticWatcher( + &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "config-logging"}}, + &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "config-observability"}}, + ), + ) + }, + wantEnvVars: nil, + }, + { + name: "stdout protocol returns nil", + cwFactory: func() *reconcilersource.ConfigWatcher { + return reconcilersource.WatchConfigurations(ctx, "test", + configmap.NewStaticWatcher( + &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "config-logging"}}, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "config-observability"}, + Data: map[string]string{"tracing-protocol": "stdout"}, + }, + ), + ) + }, + wantEnvVars: nil, + }, + { + name: "grpc protocol with sampling rate 0 sets always_off", + cwFactory: func() *reconcilersource.ConfigWatcher { + return reconcilersource.WatchConfigurations(ctx, "test", + configmap.NewStaticWatcher( + &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "config-logging"}}, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "config-observability"}, + Data: map[string]string{ + "tracing-protocol": "grpc", + "tracing-endpoint": "http://otel-collector:4317", + "tracing-sampling-rate": "0", + }, + }, + ), + ) + }, + wantEnvVars: []corev1.EnvVar{ + {Name: OTELExporterProtocolEnv, Value: "grpc"}, + {Name: OTELExporterEndpointEnv, Value: "http://otel-collector:4317"}, + {Name: OTELTracesSamplerEnv, Value: "always_off"}, + }, + }, + { + name: "grpc protocol with sampling rate 1 sets always_on", + cwFactory: func() *reconcilersource.ConfigWatcher { + return reconcilersource.WatchConfigurations(ctx, "test", + configmap.NewStaticWatcher( + &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "config-logging"}}, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "config-observability"}, + Data: map[string]string{ + "tracing-protocol": "grpc", + "tracing-endpoint": "http://otel-collector:4317", + "tracing-sampling-rate": "1", + }, + }, + ), + ) + }, + wantEnvVars: []corev1.EnvVar{ + {Name: OTELExporterProtocolEnv, Value: "grpc"}, + {Name: OTELExporterEndpointEnv, Value: "http://otel-collector:4317"}, + {Name: OTELTracesSamplerEnv, Value: "always_on"}, + }, + }, + { + name: "grpc protocol with sampling rate 0.5 sets parentbased_traceidratio", + cwFactory: func() *reconcilersource.ConfigWatcher { + return reconcilersource.WatchConfigurations(ctx, "test", + configmap.NewStaticWatcher( + &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "config-logging"}}, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "config-observability"}, + Data: map[string]string{ + "tracing-protocol": "grpc", + "tracing-endpoint": "http://otel-collector:4317", + "tracing-sampling-rate": "0.5", + }, + }, + ), + ) + }, + wantEnvVars: []corev1.EnvVar{ + {Name: OTELExporterProtocolEnv, Value: "grpc"}, + {Name: OTELExporterEndpointEnv, Value: "http://otel-collector:4317"}, + {Name: OTELTracesSamplerEnv, Value: "parentbased_traceidratio"}, + {Name: OTELTracesSamplerArgEnv, Value: "0.5"}, + }, + }, + { + name: "http/protobuf protocol with default sampling rate sets always_off", + cwFactory: func() *reconcilersource.ConfigWatcher { + return reconcilersource.WatchConfigurations(ctx, "test", + configmap.NewStaticWatcher( + &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "config-logging"}}, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "config-observability"}, + Data: map[string]string{ + "tracing-protocol": "http/protobuf", + "tracing-endpoint": "http://otel-collector:4318/v1/traces", + }, + }, + ), + ) + }, + wantEnvVars: []corev1.EnvVar{ + {Name: OTELExporterProtocolEnv, Value: "http/protobuf"}, + {Name: OTELExporterEndpointEnv, Value: "http://otel-collector:4318/v1/traces"}, + {Name: OTELTracesSamplerEnv, Value: "always_off"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cw := tt.cwFactory() + got := otelTracingEnvVars(cw) + + if len(got) != len(tt.wantEnvVars) { + t.Fatalf("otelTracingEnvVars() returned %d env vars, want %d\ngot: %+v\nwant: %+v", + len(got), len(tt.wantEnvVars), got, tt.wantEnvVars) + } + for i := range got { + if got[i].Name != tt.wantEnvVars[i].Name || got[i].Value != tt.wantEnvVars[i].Value { + t.Errorf("env var[%d] = {Name: %q, Value: %q}, want {Name: %q, Value: %q}", + i, got[i].Name, got[i].Value, tt.wantEnvVars[i].Name, tt.wantEnvVars[i].Value) + } + } + }) + } +} + +func TestJsonataDeploymentOtelEnvVars(t *testing.T) { + t.Setenv("EVENT_TRANSFORM_JSONATA_IMAGE", "quay.io/event-transform") + + ctx := context.Background() + logger := logtesting.TestLogger(t) + ctx = logging.WithLogger(ctx, logger) + + tests := []struct { + name string + tracingData map[string]string + wantOtelEnvVars map[string]string + }{ + { + name: "deployment with grpc tracing and 0.5 sampling rate", + tracingData: map[string]string{ + "tracing-protocol": "grpc", + "tracing-endpoint": "http://otel-collector:4317", + "tracing-sampling-rate": "0.5", + }, + wantOtelEnvVars: map[string]string{ + OTELExporterProtocolEnv: "grpc", + OTELExporterEndpointEnv: "http://otel-collector:4317", + OTELTracesSamplerEnv: "parentbased_traceidratio", + OTELTracesSamplerArgEnv: "0.5", + }, + }, + { + name: "deployment with grpc tracing and always_off sampling", + tracingData: map[string]string{ + "tracing-protocol": "grpc", + "tracing-endpoint": "http://otel-collector:4317", + "tracing-sampling-rate": "0", + }, + wantOtelEnvVars: map[string]string{ + OTELExporterProtocolEnv: "grpc", + OTELExporterEndpointEnv: "http://otel-collector:4317", + OTELTracesSamplerEnv: "always_off", + }, + }, + { + name: "deployment with grpc tracing and always_on sampling", + tracingData: map[string]string{ + "tracing-protocol": "grpc", + "tracing-endpoint": "http://otel-collector:4317", + "tracing-sampling-rate": "1", + }, + wantOtelEnvVars: map[string]string{ + OTELExporterProtocolEnv: "grpc", + OTELExporterEndpointEnv: "http://otel-collector:4317", + OTELTracesSamplerEnv: "always_on", + }, + }, + { + name: "deployment without tracing has no OTEL env vars", + tracingData: nil, + wantOtelEnvVars: map[string]string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cw := reconcilersource.WatchConfigurations(ctx, "eventtransform", + configmap.NewStaticWatcher( + &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "config-logging"}}, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "config-observability"}, + Data: tt.tracingData, + }, + ), + ) + + d := jsonataTestDeployment(ctx, cw) + envVars := d.Spec.Template.Spec.Containers[0].Env + + envMap := make(map[string]string) + for _, e := range envVars { + envMap[e.Name] = e.Value + } + + for name, wantValue := range tt.wantOtelEnvVars { + gotValue, ok := envMap[name] + if !ok { + t.Errorf("expected env var %q on deployment container, but it was not found", name) + continue + } + if gotValue != wantValue { + t.Errorf("env var %q = %q, want %q", name, gotValue, wantValue) + } + } + + // Verify no unexpected OTEL env vars when tracing is disabled + if tt.tracingData == nil { + otelEnvNames := []string{OTELExporterProtocolEnv, OTELExporterEndpointEnv, OTELTracesSamplerEnv, OTELTracesSamplerArgEnv} + for _, name := range otelEnvNames { + if _, ok := envMap[name]; ok { + t.Errorf("unexpected env var %q found on deployment container when tracing is disabled", name) + } + } + } + }) + } +} diff --git a/pkg/reconciler/eventtransform/resources_jsonata.go b/pkg/reconciler/eventtransform/resources_jsonata.go index c8dd0b703d9..9e5dafe5fa6 100644 --- a/pkg/reconciler/eventtransform/resources_jsonata.go +++ b/pkg/reconciler/eventtransform/resources_jsonata.go @@ -65,7 +65,7 @@ const ( // These are standard OpenTelemetry environment variables that the Node.js SDK // automatically reads to configure the trace exporter. OTELExporterEndpointEnv = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT" - OTELExporterProtocolEnv = "OTEL_EXPORTER_OTLP_PROTOCOL" + OTELExporterProtocolEnv = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL" OTELTracesSamplerEnv = "OTEL_TRACES_SAMPLER" OTELTracesSamplerArgEnv = "OTEL_TRACES_SAMPLER_ARG" ) @@ -128,18 +128,7 @@ func jsonataDeployment(ctx context.Context, withCombinedTrustBundle bool, cw *re { Name: "jsonata-event-transform", Image: image, - Env: append( - append( - []corev1.EnvVar{ - { - Name: "JSONATA_TRANSFORM_FILE_NAME", - Value: filepath.Join(JsonataExpressionPath, JsonataExpressionDataKey), - }, - }, - cw.ToEnvVars()..., - ), - otelTracingEnvVars(cw)..., - ), + Env: jsonataContainerEnvVars(cw), VolumeMounts: []corev1.VolumeMount{ { Name: expression.GetName(), @@ -472,6 +461,21 @@ func jsonataCertificate(ctx context.Context, transform *eventing.EventTransform) ) } +// jsonataContainerEnvVars builds the base environment variables for the jsonata +// container, combining the transform file path, config watcher env vars, and +// OTEL tracing env vars. +func jsonataContainerEnvVars(cw *reconcilersource.ConfigWatcher) []corev1.EnvVar { + envVars := []corev1.EnvVar{ + { + Name: "JSONATA_TRANSFORM_FILE_NAME", + Value: filepath.Join(JsonataExpressionPath, JsonataExpressionDataKey), + }, + } + envVars = append(envVars, cw.ToEnvVars()...) + envVars = append(envVars, otelTracingEnvVars(cw)...) + return envVars +} + // otelTracingEnvVars generates standard OpenTelemetry environment variables // based on the observability configuration. This allows the Node.js OTEL SDK // in the transform-jsonata container to auto-configure the trace exporter @@ -511,8 +515,19 @@ func otelTracingEnvVars(cw *reconcilersource.ConfigWatcher) []corev1.EnvVar { }) } - // Configure sampling if a sampling rate is set - if tracingCfg.SamplingRate > 0 { + // Configure sampling based on the sampling rate + switch { + case tracingCfg.SamplingRate == 0: + envVars = append(envVars, corev1.EnvVar{ + Name: OTELTracesSamplerEnv, + Value: "always_off", + }) + case tracingCfg.SamplingRate == 1: + envVars = append(envVars, corev1.EnvVar{ + Name: OTELTracesSamplerEnv, + Value: "always_on", + }) + case tracingCfg.SamplingRate > 0: envVars = append(envVars, corev1.EnvVar{ Name: OTELTracesSamplerEnv, From aaac1a755ebb42bc4c4e45ca17110fb3a524640d Mon Sep 17 00:00:00 2001 From: khushiiagrawal Date: Wed, 11 Mar 2026 22:05:48 +0530 Subject: [PATCH 3/4] fix: close TestJsonataDeploymentOtelEnvVars function in eventtransform_test.go Signed-off-by: khushiiagrawal --- pkg/reconciler/eventtransform/eventtransform_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/reconciler/eventtransform/eventtransform_test.go b/pkg/reconciler/eventtransform/eventtransform_test.go index d70ac79ccc9..cec039cda7c 100644 --- a/pkg/reconciler/eventtransform/eventtransform_test.go +++ b/pkg/reconciler/eventtransform/eventtransform_test.go @@ -2553,6 +2553,8 @@ func TestJsonataDeploymentOtelEnvVars(t *testing.T) { } }) } +} + const testAuthProxyImage = "quay.io/fake-auth-proxy" func jsonataTestDeploymentWithAuthProxy(ctx context.Context, cw *reconcilersource.ConfigWatcher, opts ...DeploymentOption) *appsv1.Deployment { From 28094796afc2965ff290b223842269d80e21903a Mon Sep 17 00:00:00 2001 From: khushiiagrawal Date: Thu, 12 Mar 2026 00:16:26 +0530 Subject: [PATCH 4/4] fix the formatting Signed-off-by: khushiiagrawal --- pkg/reconciler/eventtransform/resources_jsonata.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/reconciler/eventtransform/resources_jsonata.go b/pkg/reconciler/eventtransform/resources_jsonata.go index e96b94b7d96..4bde3428b84 100644 --- a/pkg/reconciler/eventtransform/resources_jsonata.go +++ b/pkg/reconciler/eventtransform/resources_jsonata.go @@ -69,10 +69,10 @@ const ( // OTEL environment variable names for tracing auto-configuration // These are standard OpenTelemetry environment variables that the Node.js SDK // automatically reads to configure the trace exporter. - OTELExporterEndpointEnv = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT" - OTELExporterProtocolEnv = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL" - OTELTracesSamplerEnv = "OTEL_TRACES_SAMPLER" - OTELTracesSamplerArgEnv = "OTEL_TRACES_SAMPLER_ARG" + OTELExporterEndpointEnv = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT" + OTELExporterProtocolEnv = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL" + OTELTracesSamplerEnv = "OTEL_TRACES_SAMPLER" + OTELTracesSamplerArgEnv = "OTEL_TRACES_SAMPLER_ARG" JsonataAuthProxyRoleBindingName = "eventing-auth-proxy-eventtransform" ) @@ -134,7 +134,7 @@ func jsonataDeployment(ctx context.Context, authProxyImage string, withCombinedT { Name: "jsonata-event-transform", Image: image, - Env: jsonataContainerEnvVars(cw), + Env: jsonataContainerEnvVars(cw), VolumeMounts: []corev1.VolumeMount{ { Name: expression.GetName(),