This is an automated email from the ASF dual-hosted git repository.

github-actions[bot] pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git


The following commit(s) were added to refs/heads/master by this push:
     new 6f807d51 feat: configure access logs with Telemetry API (#1021)
6f807d51 is described below

commit 6f807d5175cdfe6a831e29ffeb6326d352aef36a
Author: mfordjody <[email protected]>
AuthorDate: Thu Aug 20 22:00:35 2026 +0800

    feat: configure access logs with Telemetry API (#1021)
---
 .../pkg/bootstrap/inherent_grpc_controller.go      | 66 ++++++++++++++++------
 .../pkg/bootstrap/inherent_grpc_controller_test.go | 17 ++++++
 .../config/kube/gateway/deployment_controller.go   | 48 ++++++++++++++--
 .../kube/gateway/deployment_controller_test.go     | 11 ++++
 go.mod                                             | 10 ++--
 go.sum                                             | 18 +++---
 manifests/charts/base/files/crd-all.gen.yaml       | 54 ++++++++++++++++++
 manifests/charts/dubbod/files/kube-gateway.yaml    | 16 ++++++
 pkg/config/telemetry/telemetry.go                  | 37 ++++++++++++
 pkg/config/telemetry/telemetry_test.go             | 39 +++++++++++++
 pkg/config/validation/validators.go                | 44 +++++++++++++++
 11 files changed, 326 insertions(+), 34 deletions(-)

diff --git a/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller.go 
b/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller.go
index 7c57f583..231c3393 100644
--- a/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller.go
+++ b/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller.go
@@ -372,7 +372,17 @@ type inherentGRPCRuntimeConfig struct {
 }
 
 type inherentGRPCTelemetryRuntimeConfig struct {
-       Metrics *inherentGRPCMetricsRuntimeConfig `json:"metrics,omitempty"`
+       Metrics *inherentGRPCMetricsRuntimeConfig  `json:"metrics,omitempty"`
+       Logging []inherentGRPCLoggingRuntimeConfig `json:"logging,omitempty"`
+}
+
+type inherentGRPCLoggingRuntimeConfig struct {
+       Providers        []string          `json:"providers,omitempty"`
+       Disabled         bool              `json:"disabled"`
+       Mode             string            `json:"mode,omitempty"`
+       FilterExpression string            `json:"filterExpression,omitempty"`
+       Tags             map[string]string `json:"tags,omitempty"`
+       Endpoint         string            `json:"endpoint,omitempty"`
 }
 
 type inherentGRPCMetricsRuntimeConfig struct {
@@ -771,28 +781,52 @@ func buildRuntimeConfigJSON(
 }
 
 func inherentGRPCTelemetryConfig(effective telemetryconfig.EffectiveTracing) 
*inherentGRPCTelemetryRuntimeConfig {
-       if !effective.MetricsConfigured {
+       if !effective.MetricsConfigured && !effective.LoggingConfigured {
                return nil
        }
-       metrics := &inherentGRPCMetricsRuntimeConfig{
-               Enabled:   effective.MetricsEnabled(),
-               Providers: append([]string(nil), effective.MetricProviders...),
-               Rules:     make([]inherentGRPCMetricRuleRuntimeConfig, 0, 
len(effective.MetricRules)),
+       config := &inherentGRPCTelemetryRuntimeConfig{
+               Logging: make([]inherentGRPCLoggingRuntimeConfig, 0, 
len(effective.Logging)),
        }
-       for _, rule := range effective.MetricRules {
-               runtimeRule := inherentGRPCMetricRuleRuntimeConfig{
-                       Metric: rule.Metric.String(),
-                       Scope:  rule.Scope.String(),
+       if effective.MetricsConfigured {
+               config.Metrics = &inherentGRPCMetricsRuntimeConfig{
+                       Enabled:   effective.MetricsEnabled(),
+                       Providers: append([]string(nil), 
effective.MetricProviders...),
+                       Rules:     make([]inherentGRPCMetricRuleRuntimeConfig, 
0, len(effective.MetricRules)),
                }
-               if len(rule.Tags) > 0 {
-                       runtimeRule.Tags = 
make(map[string]inherentGRPCTagOverrideRuntimeConfig, len(rule.Tags))
-                       for _, tag := range rule.Tags {
-                               runtimeRule.Tags[tag.Name] = 
inherentGRPCTagOverrideRuntimeConfig{Action: tag.Action.String()}
+               for _, rule := range effective.MetricRules {
+                       runtimeRule := inherentGRPCMetricRuleRuntimeConfig{
+                               Metric: rule.Metric.String(),
+                               Scope:  rule.Scope.String(),
                        }
+                       if len(rule.Tags) > 0 {
+                               runtimeRule.Tags = 
make(map[string]inherentGRPCTagOverrideRuntimeConfig, len(rule.Tags))
+                               for _, tag := range rule.Tags {
+                                       runtimeRule.Tags[tag.Name] = 
inherentGRPCTagOverrideRuntimeConfig{Action: tag.Action.String()}
+                               }
+                       }
+                       config.Metrics.Rules = append(config.Metrics.Rules, 
runtimeRule)
+               }
+       }
+       for _, rule := range effective.Logging {
+               runtimeRule := inherentGRPCLoggingRuntimeConfig{
+                       Providers:        append([]string(nil), 
rule.Providers...),
+                       Disabled:         rule.Disabled,
+                       Mode:             rule.Mode.String(),
+                       FilterExpression: rule.FilterExpression,
+                       Tags:             make(map[string]string, 
len(rule.Tags)),
+               }
+               if runtimeRule.Mode == "MODE_UNSPECIFIED" {
+                       runtimeRule.Mode = "CLIENT_AND_SERVER"
+               }
+               if len(rule.Providers) > 0 {
+                       runtimeRule.Endpoint = 
telemetryconfig.ProviderEndpoint(rule.Providers[0], 
constants.DubboSystemNamespace)
+               }
+               for _, tag := range rule.Tags {
+                       runtimeRule.Tags[tag.Name] = tag.Value
                }
-               metrics.Rules = append(metrics.Rules, runtimeRule)
+               config.Logging = append(config.Logging, runtimeRule)
        }
-       return &inherentGRPCTelemetryRuntimeConfig{Metrics: metrics}
+       return config
 }
 
 func (c *inherentGRPCWorkloadController) buildRuntimeTrafficConfig() 
([]inherentGRPCServiceRuntimeConfig, []inherentGRPCRouteRuntimeConfig) {
diff --git a/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller_test.go 
b/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller_test.go
index c965b697..073c3c28 100644
--- a/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller_test.go
+++ b/dubbod/discovery/pkg/bootstrap/inherent_grpc_controller_test.go
@@ -162,6 +162,13 @@ func TestBuildRuntimeConfigJSON(t *testing.T) {
                                Action: telemetryapi.TagOverride_REMOVE,
                        }},
                }},
+               LoggingConfigured: true,
+               Logging: []telemetryconfig.LoggingRule{{
+                       Providers:        
[]string{telemetryconfig.OTELLogProvider},
+                       Mode:             telemetryapi.Logging_Match_SERVER,
+                       FilterExpression: "response.code >= 500",
+                       Tags:             []telemetryconfig.Tag{{Name: 
"environment", Value: "test"}},
+               }},
        }
        data, err := buildRuntimeConfigJSON(workload, nil, nil, 
effectiveTelemetry)
        if err != nil {
@@ -249,6 +256,16 @@ func TestBuildRuntimeConfigJSON(t *testing.T) {
        if tag := rule.Tags["grpc_response_status"]; tag.Action != "REMOVE" {
                t.Fatalf("grpc_response_status override = %#v", tag)
        }
+       if len(got.Telemetry.Logging) != 1 {
+               t.Fatalf("telemetry logging = %#v, want one", 
got.Telemetry.Logging)
+       }
+       logging := got.Telemetry.Logging[0]
+       if logging.Mode != "SERVER" ||
+               logging.Endpoint != 
"http://opentelemetry-collector.dubbo-system.svc:4317"; ||
+               logging.FilterExpression != "response.code >= 500" ||
+               logging.Tags["environment"] != "test" {
+               t.Fatalf("telemetry logging = %#v", logging)
+       }
 }
 
 func TestInherentTelemetrySerializesAllStandardMetrics(t *testing.T) {
diff --git a/dubbod/discovery/pkg/config/kube/gateway/deployment_controller.go 
b/dubbod/discovery/pkg/config/kube/gateway/deployment_controller.go
index a91606c1..beabb6b8 100644
--- a/dubbod/discovery/pkg/config/kube/gateway/deployment_controller.go
+++ b/dubbod/discovery/pkg/config/kube/gateway/deployment_controller.go
@@ -424,11 +424,15 @@ func (d *DeploymentController) configureGateway(log 
*dubbolog.Logger, gw gateway
                ClusterID:           string(d.clusterID),
                DomainSuffix:        d.domainSuffix(),
                OtelEndpoint:        observability.OtelEndpoint,
+               OtelLogsEndpoint:    observability.OtelLogsEndpoint,
                OtelServiceName:     observability.OtelServiceName,
                OtelSampling:        observability.OtelSampling,
                OtelTags:            observability.OtelTags,
                AccessLog:           observability.AccessLog,
                AccessLogFormat:     observability.AccessLogFormat,
+               AccessLogMode:       observability.AccessLogMode,
+               AccessLogFilter:     observability.AccessLogFilter,
+               AccessLogTags:       observability.AccessLogTags,
 
                ActivationControlPlane: d.activationControlPlane(),
                ActivationHoldTimeout:  features.ActivationHoldTimeout,
@@ -506,11 +510,15 @@ type TemplateInput struct {
        ClusterID           string
        DomainSuffix        string
        OtelEndpoint        string
+       OtelLogsEndpoint    string
        OtelServiceName     string
        OtelSampling        string
        OtelTags            string
        AccessLog           string
        AccessLogFormat     string
+       AccessLogMode       string
+       AccessLogFilter     string
+       AccessLogTags       string
        // ActivationControlPlane is the address a gateway reports pending 
requests
        // to so that scaled-to-zero targets get activated. Empty turns the 
feature
        // off in the data plane; the gateway then fails such a request 
outright, as
@@ -520,12 +528,16 @@ type TemplateInput struct {
 }
 
 type gatewayObservabilityConfig struct {
-       OtelEndpoint    string
-       OtelServiceName string
-       OtelSampling    string
-       OtelTags        string
-       AccessLog       string
-       AccessLogFormat string
+       OtelEndpoint     string
+       OtelLogsEndpoint string
+       OtelServiceName  string
+       OtelSampling     string
+       OtelTags         string
+       AccessLog        string
+       AccessLogFormat  string
+       AccessLogMode    string
+       AccessLogFilter  string
+       AccessLogTags    string
 }
 
 // observabilityConfigForGateway resolves the meshlevel, namespace, and
@@ -554,6 +566,30 @@ func resolveGatewayObservability(gw gateway.Gateway, 
meshNamespace string, resou
        if effective.Configured && !effective.Disabled() {
                cfg.OtelEndpoint = 
telemetryconfig.ProviderEndpoint(effective.Provider(), meshNamespace)
        }
+       for _, logging := range effective.Logging {
+               mode := logging.Mode.String()
+               if mode == "MODE_UNSPECIFIED" {
+                       mode = "CLIENT_AND_SERVER"
+               }
+               if mode != "SERVER" && mode != "CLIENT_AND_SERVER" {
+                       continue
+               }
+               cfg.AccessLog = strconv.FormatBool(!logging.Disabled)
+               cfg.AccessLogMode = mode
+               cfg.AccessLogFilter = logging.FilterExpression
+               tags := make(map[string]string, len(logging.Tags))
+               for _, tag := range logging.Tags {
+                       tags[tag.Name] = tag.Value
+               }
+               if len(tags) > 0 {
+                       data, _ := json.Marshal(tags)
+                       cfg.AccessLogTags = string(data)
+               }
+               if len(logging.Providers) > 0 && !logging.Disabled {
+                       cfg.OtelLogsEndpoint = 
telemetryconfig.ProviderEndpoint(logging.Providers[0], meshNamespace)
+               }
+               break
+       }
        return cfg
 }
 
diff --git 
a/dubbod/discovery/pkg/config/kube/gateway/deployment_controller_test.go 
b/dubbod/discovery/pkg/config/kube/gateway/deployment_controller_test.go
index e429444e..e51b587d 100644
--- a/dubbod/discovery/pkg/config/kube/gateway/deployment_controller_test.go
+++ b/dubbod/discovery/pkg/config/kube/gateway/deployment_controller_test.go
@@ -856,6 +856,11 @@ func TestResolveGatewayObservabilityTelemetryHierarchy(t 
*testing.T) {
                                Providers:                
[]*apitelemetry.Tracing_TracingProvider{{Name: "localtrace"}},
                                Tags:                     
[]*apitelemetry.Tracing_Tag{{Name: "foo", Value: "bar"}},
                                RandomSamplingPercentage: 
wrapperspb.Double(100),
+                       }}, Logging: []*apitelemetry.Logging{{
+                               Providers: 
[]*apitelemetry.Logging_LoggingProvider{{Name: 
telemetryconfig.OTELLogProvider}},
+                               Match:     &apitelemetry.Logging_Match{Mode: 
apitelemetry.Logging_Match_SERVER},
+                               Filter:    
&apitelemetry.Logging_Filter{Expression: "response.code >= 500"},
+                               Tags:      []*apitelemetry.Logging_Tag{{Name: 
"environment", Value: "test"}},
                        }}},
                },
                {
@@ -880,6 +885,12 @@ func TestResolveGatewayObservabilityTelemetryHierarchy(t 
*testing.T) {
        if cfg.OtelTags != `{"userId":"unknown"}` {
                t.Fatalf("otel tags = %q", cfg.OtelTags)
        }
+       if cfg.OtelLogsEndpoint != 
"http://opentelemetry-collector.dubbo-system.svc:4317"; ||
+               cfg.AccessLog != "true" || cfg.AccessLogMode != "SERVER" ||
+               cfg.AccessLogFilter != "response.code >= 500" ||
+               cfg.AccessLogTags != `{"environment":"test"}` {
+               t.Fatalf("access logging = %#v", cfg)
+       }
        resources = append(resources, telemetryconfig.Resource{
                Name: "workload-override", Namespace: "app", CreationTimestamp: 
time.Unix(3, 0),
                Spec: &apitelemetry.Telemetry{
diff --git a/go.mod b/go.mod
index 39c7967e..0f4f09f1 100644
--- a/go.mod
+++ b/go.mod
@@ -41,9 +41,9 @@ require (
        github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
        github.com/hashicorp/go-multierror v1.1.1
        github.com/hashicorp/golang-lru/v2 v2.0.7
-       github.com/kdubbo/api v0.0.0-20260814141555-d9b670d33d9f
-       github.com/kdubbo/client-go v0.0.0-20260814141742-c761bafa7cf3
-       github.com/kdubbo/xds-api v0.0.0-20260814172110-c45be7c324a3
+       github.com/kdubbo/api v0.0.0-20260820123851-c3a7c138547d
+       github.com/kdubbo/client-go v0.0.0-20260820124012-0079f2cf2b1d
+       github.com/kdubbo/xds-api v0.0.0-20260820125224-2e2719c54121
        github.com/prometheus/client_golang v1.23.2
        github.com/prometheus/client_model v0.6.2
        github.com/spf13/cobra v1.10.2
@@ -51,7 +51,7 @@ require (
        github.com/stoewer/go-strcase v1.3.1
        go.uber.org/atomic v1.11.0
        golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
-       golang.org/x/net v0.56.0
+       golang.org/x/net v0.57.0
        golang.org/x/sys v0.47.0
        golang.org/x/term v0.45.0
        golang.org/x/time v0.15.0
@@ -106,6 +106,7 @@ require (
        github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
        github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // 
indirect
        github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // 
indirect
+       github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
        github.com/hashicorp/errwrap v1.1.0 // indirect
        github.com/huandu/xstrings v1.5.0 // indirect
        github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -136,6 +137,7 @@ require (
        github.com/spf13/cast v1.8.0 // indirect
        github.com/x448/float16 v0.8.4 // indirect
        github.com/xlab/treeprint v1.2.0 // indirect
+       go.opentelemetry.io/proto/otlp v1.9.0 // indirect
        go.yaml.in/yaml/v2 v2.4.3 // indirect
        go.yaml.in/yaml/v3 v3.0.4 // indirect
        golang.org/x/crypto v0.54.0 // indirect
diff --git a/go.sum b/go.sum
index 24e59654..b54475b4 100644
--- a/go.sum
+++ b/go.sum
@@ -1007,6 +1007,7 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod 
h1:o//XUCC/F+yRGJoPO/VU
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod 
h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod 
h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod 
h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 
h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod 
h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
 github.com/hamba/avro/v2 v2.17.2/go.mod 
h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo=
 github.com/hashicorp/consul/api v1.3.0/go.mod 
h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
@@ -1071,12 +1072,12 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod 
h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
 github.com/jung-kurt/gofpdf v1.0.0/go.mod 
h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod 
h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod 
h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
-github.com/kdubbo/api v0.0.0-20260814141555-d9b670d33d9f 
h1:T1jlRZ+ulFTUGziUnTjMvNEtiknaHQr4vQYviz4U/kQ=
-github.com/kdubbo/api v0.0.0-20260814141555-d9b670d33d9f/go.mod 
h1:8BtJiIovg7QCPsCxXcw3gDf922VcvYq5ihOSvj49Rq8=
-github.com/kdubbo/client-go v0.0.0-20260814141742-c761bafa7cf3 
h1:vTpsq7IAuUqKz05NoCWpDu0MY5SdjpH5NXZpj+l4I9Q=
-github.com/kdubbo/client-go v0.0.0-20260814141742-c761bafa7cf3/go.mod 
h1:wCARoHJuh9ccSRQYlGXzPyMfUMYavnRot05dKh1N4KY=
-github.com/kdubbo/xds-api v0.0.0-20260814172110-c45be7c324a3 
h1:ypir1ZNYdAKOuWokpObAdUtCDXLD3yqP1AOqCURe3WU=
-github.com/kdubbo/xds-api v0.0.0-20260814172110-c45be7c324a3/go.mod 
h1:o2HDUgL1ntaDbWomZ4cD2tt8jBamuG2qRtjXOa1zZ0Q=
+github.com/kdubbo/api v0.0.0-20260820123851-c3a7c138547d 
h1:FCV7OMp3FZmQzmJAM75SSI2SSVHsgbDxHz/pyCQGWVw=
+github.com/kdubbo/api v0.0.0-20260820123851-c3a7c138547d/go.mod 
h1:8BtJiIovg7QCPsCxXcw3gDf922VcvYq5ihOSvj49Rq8=
+github.com/kdubbo/client-go v0.0.0-20260820124012-0079f2cf2b1d 
h1:bfMl7kxZq9Gp83MNh/4btIjK4FiGzUNbHNJQNDcZL+w=
+github.com/kdubbo/client-go v0.0.0-20260820124012-0079f2cf2b1d/go.mod 
h1:XiHz2FHeoNOavRxt2EQLR9WC6Jl69TzM5MwQrS4ToqU=
+github.com/kdubbo/xds-api v0.0.0-20260820125224-2e2719c54121 
h1:4yzXRhnMrCm6236P3dqU2f7CJpz7vzIQQkl5ye44HnQ=
+github.com/kdubbo/xds-api v0.0.0-20260820125224-2e2719c54121/go.mod 
h1:TilAt91qTzM3pArPae5S/1uRTdH2adhGnFIUkTBaSkk=
 github.com/kisielk/errcheck v1.1.0/go.mod 
h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.5.0/go.mod 
h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod 
h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -1557,6 +1558,7 @@ go.opentelemetry.io/proto/otlp v0.19.0/go.mod 
h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI
 go.opentelemetry.io/proto/otlp v1.0.0/go.mod 
h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
 go.opentelemetry.io/proto/otlp v1.7.0/go.mod 
h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
 go.opentelemetry.io/proto/otlp v1.7.1/go.mod 
h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
+go.opentelemetry.io/proto/otlp v1.9.0 
h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
 go.opentelemetry.io/proto/otlp v1.9.0/go.mod 
h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
 go.uber.org/atomic v1.3.2/go.mod 
h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod 
h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@@ -1858,8 +1860,8 @@ golang.org/x/net v0.49.0/go.mod 
h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
 golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
 golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
 golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
-golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
-golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
+golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
+golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod 
h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod 
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod 
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
diff --git a/manifests/charts/base/files/crd-all.gen.yaml 
b/manifests/charts/base/files/crd-all.gen.yaml
index 95713abb..1e88e4f1 100644
--- a/manifests/charts/base/files/crd-all.gen.yaml
+++ b/manifests/charts/base/files/crd-all.gen.yaml
@@ -1601,6 +1601,60 @@ spec:
             description: 'Telemetry configuration for Inherent workloads. See 
more
               details at: '
             properties:
+              logging:
+                description: Logging configures access logging for selected 
workloads.
+                items:
+                  properties:
+                    disabled:
+                      description: Disables access logging for selected 
workloads.
+                      nullable: true
+                      type: boolean
+                    filter:
+                      description: Filters access logs using a CEL expression.
+                      properties:
+                        expression:
+                          description: REQUIRED.
+                          type: string
+                      type: object
+                    match:
+                      description: Restricts access logs to a reporter side.
+                      properties:
+                        mode:
+                          description: |-
+                            Reporter side selected for access logging.
+
+                            Valid Options: CLIENT, SERVER, CLIENT_AND_SERVER
+                          enum:
+                          - MODE_UNSPECIFIED
+                          - CLIENT
+                          - SERVER
+                          - CLIENT_AND_SERVER
+                          type: string
+                      type: object
+                    providers:
+                      description: Providers used for access log reporting.
+                      items:
+                        properties:
+                          name:
+                            description: REQUIRED.
+                            type: string
+                        type: object
+                      type: array
+                    tags:
+                      description: Static custom attributes added to generated 
access
+                        logs.
+                      items:
+                        properties:
+                          name:
+                            description: REQUIRED.
+                            type: string
+                          value:
+                            description: Static access log attribute value.
+                            type: string
+                        type: object
+                      type: array
+                  type: object
+                type: array
               metrics:
                 description: Metrics configures metric generation for selected 
workloads.
                 items:
diff --git a/manifests/charts/dubbod/files/kube-gateway.yaml 
b/manifests/charts/dubbod/files/kube-gateway.yaml
index 4d478c68..ce28a8d4 100644
--- a/manifests/charts/dubbod/files/kube-gateway.yaml
+++ b/manifests/charts/dubbod/files/kube-gateway.yaml
@@ -160,11 +160,27 @@ spec:
 {{- if .OtelEndpoint }}
         - name: DXGATE_OTEL_ENDPOINT
           value: {{ .OtelEndpoint | quote }}
+{{- end }}
+{{- if .OtelLogsEndpoint }}
+        - name: DXGATE_OTEL_LOGS_ENDPOINT
+          value: {{ .OtelLogsEndpoint | quote }}
 {{- end }}
         - name: DXGATE_ACCESS_LOG
           value: {{ .AccessLog | quote }}
         - name: DXGATE_ACCESS_LOG_FORMAT
           value: {{ .AccessLogFormat | quote }}
+{{- if .AccessLogMode }}
+        - name: DXGATE_ACCESS_LOG_MODE
+          value: {{ .AccessLogMode | quote }}
+{{- end }}
+{{- if .AccessLogFilter }}
+        - name: DXGATE_ACCESS_LOG_FILTER
+          value: {{ .AccessLogFilter | quote }}
+{{- end }}
+{{- if .AccessLogTags }}
+        - name: DXGATE_ACCESS_LOG_TAGS
+          value: {{ .AccessLogTags | quote }}
+{{- end }}
 {{- if .ActivationControlPlane }}
         # On-demand activation. Absent, the gateway fails a request for a
         # scaled-to-zero backend immediately, which is the behaviour every
diff --git a/pkg/config/telemetry/telemetry.go 
b/pkg/config/telemetry/telemetry.go
index c243c930..9ae7bee6 100644
--- a/pkg/config/telemetry/telemetry.go
+++ b/pkg/config/telemetry/telemetry.go
@@ -33,6 +33,7 @@ import (
 const (
        LocalTraceProvider = "localtrace"
        PrometheusProvider = "prometheus"
+       OTELLogProvider    = "otel"
        OTLPPort           = 4317
 )
 
@@ -59,6 +60,14 @@ type MetricRule struct {
        Tags   []MetricTagOverride
 }
 
+type LoggingRule struct {
+       Providers        []string
+       Disabled         bool
+       Mode             api.Logging_Match_Mode
+       FilterExpression string
+       Tags             []Tag
+}
+
 type EffectiveTracing struct {
        Configured               bool
        Providers                []string
@@ -69,6 +78,8 @@ type EffectiveTracing struct {
        MetricProviders          []string
        EnableMetrics            *bool
        MetricRules              []MetricRule
+       LoggingConfigured        bool
+       Logging                  []LoggingRule
 }
 
 func ResourcesFromConfigs(configs []config.Config) []Resource {
@@ -163,6 +174,29 @@ func matches(selector, labels map[string]string) bool {
 }
 
 func apply(result *EffectiveTracing, spec *api.Telemetry) {
+       if len(spec.GetLogging()) > 0 {
+               result.LoggingConfigured = true
+               result.Logging = make([]LoggingRule, 0, len(spec.GetLogging()))
+               for _, logging := range spec.GetLogging() {
+                       if logging == nil {
+                               continue
+                       }
+                       effective := LoggingRule{
+                               Disabled:         
logging.GetDisabled().GetValue(),
+                               Mode:             logging.GetMatch().GetMode(),
+                               FilterExpression: 
logging.GetFilter().GetExpression(),
+                               Providers:        make([]string, 0, 
len(logging.GetProviders())),
+                               Tags:             make([]Tag, 0, 
len(logging.GetTags())),
+                       }
+                       for _, provider := range logging.GetProviders() {
+                               effective.Providers = 
append(effective.Providers, provider.GetName())
+                       }
+                       for _, tag := range logging.GetTags() {
+                               effective.Tags = append(effective.Tags, 
Tag{Name: tag.GetName(), Value: tag.GetValue()})
+                       }
+                       result.Logging = append(result.Logging, effective)
+               }
+       }
        for _, metrics := range spec.GetMetrics() {
                if metrics == nil {
                        continue
@@ -300,6 +334,9 @@ func ProviderEndpoint(provider, meshNamespace string) 
string {
        if strings.EqualFold(service, LocalTraceProvider) {
                service = "tracing"
        }
+       if strings.EqualFold(service, OTELLogProvider) {
+               service = "opentelemetry-collector"
+       }
        if service == "" {
                return ""
        }
diff --git a/pkg/config/telemetry/telemetry_test.go 
b/pkg/config/telemetry/telemetry_test.go
index b96c754b..95efde25 100644
--- a/pkg/config/telemetry/telemetry_test.go
+++ b/pkg/config/telemetry/telemetry_test.go
@@ -139,6 +139,42 @@ func TestResolveMetricsRulesOverride(t *testing.T) {
        }
 }
 
+func TestResolveLoggingOverride(t *testing.T) {
+       resources := []Resource{
+               {
+                       Name: "mesh-default", Namespace: "dubbo-system",
+                       Spec: &api.Telemetry{Logging: []*api.Logging{{
+                               Providers: 
[]*api.Logging_LoggingProvider{{Name: OTELLogProvider}},
+                               Tags:      []*api.Logging_Tag{{Name: "mesh", 
Value: "default"}},
+                       }}},
+               },
+               {
+                       Name: "workload-override", Namespace: "myapp",
+                       Spec: &api.Telemetry{
+                               Selector: 
&typeapi.WorkloadSelector{MatchLabels: map[string]string{"app": "frontend"}},
+                               Logging: []*api.Logging{{
+                                       Providers: 
[]*api.Logging_LoggingProvider{{Name: OTELLogProvider}},
+                                       Match:     &api.Logging_Match{Mode: 
api.Logging_Match_SERVER},
+                                       Filter:    
&api.Logging_Filter{Expression: "response.code >= 500"},
+                                       Tags:      []*api.Logging_Tag{{Name: 
"environment", Value: "test"}},
+                               }},
+                       },
+               },
+       }
+
+       got := Resolve(resources, "dubbo-system", "myapp", 
map[string]string{"app": "frontend"})
+       if !got.LoggingConfigured || len(got.Logging) != 1 {
+               t.Fatalf("logging = %#v", got.Logging)
+       }
+       rule := got.Logging[0]
+       if len(rule.Providers) != 1 || rule.Providers[0] != OTELLogProvider ||
+               rule.Mode != api.Logging_Match_SERVER ||
+               rule.FilterExpression != "response.code >= 500" ||
+               len(rule.Tags) != 1 || rule.Tags[0] != (Tag{Name: 
"environment", Value: "test"}) {
+               t.Fatalf("logging rule = %#v", rule)
+       }
+}
+
 func TestMeshlevelSelectorIsIgnored(t *testing.T) {
        resources := []Resource{{
                Name: "invalid", Namespace: "dubbo-system",
@@ -157,4 +193,7 @@ func TestProviderEndpoint(t *testing.T) {
        if got, want := ProviderEndpoint("localtrace", "dubbo-system"), 
"http://tracing.dubbo-system.svc:4317";; got != want {
                t.Fatalf("endpoint = %q, want %q", got, want)
        }
+       if got, want := ProviderEndpoint("otel", "dubbo-system"), 
"http://opentelemetry-collector.dubbo-system.svc:4317";; got != want {
+               t.Fatalf("logging endpoint = %q, want %q", got, want)
+       }
 }
diff --git a/pkg/config/validation/validators.go 
b/pkg/config/validation/validators.go
index 750bde73..92933ccd 100644
--- a/pkg/config/validation/validators.go
+++ b/pkg/config/validation/validators.go
@@ -593,6 +593,50 @@ var ValidateTelemetry = 
RegisterValidateFunc("ValidateTelemetry",
                if cfg.Namespace == constants.DubboSystemNamespace && 
spec.GetSelector() != nil {
                        v = appendValidation(v, fmt.Errorf("selector is not 
allowed on meshlevel Telemetry in namespace %q", 
constants.DubboSystemNamespace))
                }
+               for i, logging := range spec.GetLogging() {
+                       if logging == nil {
+                               v = appendValidation(v, fmt.Errorf("logging[%d] 
must not be null", i))
+                               continue
+                       }
+                       providers := map[string]struct{}{}
+                       for j, provider := range logging.GetProviders() {
+                               name := strings.TrimSpace(provider.GetName())
+                               if name == "" {
+                                       v = appendValidation(v, 
fmt.Errorf("logging[%d].providers[%d].name must be set", i, j))
+                                       continue
+                               }
+                               if name != telemetryconfig.OTELLogProvider {
+                                       v = appendValidation(v, 
fmt.Errorf("logging[%d].providers[%d].name %q is unsupported", i, j, name))
+                               }
+                               if _, found := providers[name]; found {
+                                       v = appendValidation(v, 
fmt.Errorf("logging[%d].providers[%d].name %q is duplicated", i, j, name))
+                               }
+                               providers[name] = struct{}{}
+                       }
+                       switch logging.GetMatch().GetMode() {
+                       case telemetry.Logging_Match_MODE_UNSPECIFIED,
+                               telemetry.Logging_Match_CLIENT,
+                               telemetry.Logging_Match_SERVER,
+                               telemetry.Logging_Match_CLIENT_AND_SERVER:
+                       default:
+                               v = appendValidation(v, 
fmt.Errorf("logging[%d].match.mode is invalid", i))
+                       }
+                       if logging.GetFilter() != nil && 
strings.TrimSpace(logging.GetFilter().GetExpression()) == "" {
+                               v = appendValidation(v, 
fmt.Errorf("logging[%d].filter.expression must be set", i))
+                       }
+                       tagNames := map[string]struct{}{}
+                       for j, tag := range logging.GetTags() {
+                               name := strings.TrimSpace(tag.GetName())
+                               if name == "" {
+                                       v = appendValidation(v, 
fmt.Errorf("logging[%d].tags[%d].name must be set", i, j))
+                                       continue
+                               }
+                               if _, found := tagNames[name]; found {
+                                       v = appendValidation(v, 
fmt.Errorf("logging[%d].tags[%d].name %q is duplicated", i, j, name))
+                               }
+                               tagNames[name] = struct{}{}
+                       }
+               }
                for i, m := range spec.GetMetrics() {
                        if m == nil {
                                v = appendValidation(v, fmt.Errorf("metrics[%d] 
must not be null", i))

Reply via email to