This is an automated email from the ASF dual-hosted git repository.
Alanxtl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
The following commit(s) were added to refs/heads/develop by this push:
new 17ce297fb feat(metadata): classify errors and add useful context
(#3707)
17ce297fb is described below
commit 17ce297fbf55de96c9d6050d83443b44aa116a82
Author: Modo <[email protected]>
AuthorDate: Wed Sep 2 21:23:39 2026 +0800
feat(metadata): classify errors and add useful context (#3707)
* feat: classify errors, enrich them with useful context, and add
failure-path tests
* fix test
---
metadata/client.go | 28 ++++++---
metadata/client_test.go | 69 +++++++++++++++++++++-
metadata/mapping/metadata/service_name_mapping.go | 35 +++++++++--
.../mapping/metadata/service_name_mapping_test.go | 42 ++++++++++++-
metadata/report_instance.go | 23 +++++++-
metadata/report_instance_test.go | 35 +++++++++--
.../servicediscovery/service_discovery_registry.go | 6 +-
.../service_discovery_registry_test.go | 8 ++-
.../service_instances_changed_listener_impl.go | 38 +++++++++---
...service_instances_changed_listener_impl_test.go | 17 +++++-
10 files changed, 262 insertions(+), 39 deletions(-)
diff --git a/metadata/client.go b/metadata/client.go
index 15169d605..791ce0a49 100644
--- a/metadata/client.go
+++ b/metadata/client.go
@@ -45,11 +45,12 @@ const defaultTimeout = "5s" // s
func GetMetadataFromMetadataReport(revision string, instance
registry.ServiceInstance, registryId string) (*info.MetadataInfo, error) {
report := GetMetadataReportByRegistry(registryId)
if report == nil {
- return nil, fmt.Errorf("no metadata report instance found for
registryId=%s, please check metadata-report configuration", registryId)
+ return nil, fmt.Errorf("metadata_report failed: operation=get
app=%s revision=%s registry_id=%s storage_type=%s: no metadata report instance
found, please check metadata-report configuration",
+ instance.GetServiceName(), revision, registryId,
constant.RemoteMetadataStorageType)
}
meta, err := report.GetAppMetadata(instance.GetServiceName(), revision)
if err != nil {
- return nil, fmt.Errorf("failed to get app metadata app=%s
revision=%s: %w", instance.GetServiceName(), revision, err)
+ return nil, fmt.Errorf("%w; registry_id=%s", err, registryId)
}
return meta, nil
}
@@ -64,18 +65,25 @@ func GetMetadataFromRpcWithContext(ctx context.Context,
revision string, instanc
if ctx == nil {
ctx = context.Background()
}
+ storageType := constant.DefaultMetadataStorageType
+ if instanceMetadata := instance.GetMetadata(); instanceMetadata != nil
&& instanceMetadata[constant.MetadataStorageTypePropertyName] != "" {
+ storageType =
instanceMetadata[constant.MetadataStorageTypePropertyName]
+ }
if err := ctx.Err(); err != nil {
- return nil, err
+ return nil, fmt.Errorf("rpc_metadata failed: app=%s revision=%s
instance_id=%s host=%s storage_type=%s: %w",
+ instance.GetServiceName(), revision, instance.GetID(),
instance.GetHost(), storageType, err)
}
url, err := buildStandardMetadataServiceURL(instance)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("url_construction failed: app=%s
revision=%s instance_id=%s host=%s storage_type=%s: %w",
+ instance.GetServiceName(), revision, instance.GetID(),
instance.GetHost(), storageType, err)
}
url.SetParam(constant.TimeoutKey, defaultTimeout)
p := extension.GetProtocol(url.Protocol)
invoker := p.Refer(url)
if invoker == nil { // can't connect instance
- return nil, errors.New("can not connect to remote metadata
service host: " + url.Ip)
+ return nil, fmt.Errorf("rpc_metadata failed: app=%s revision=%s
instance_id=%s host=%s storage_type=%s: can not connect to remote metadata
service",
+ instance.GetServiceName(), revision, instance.GetID(),
instance.GetHost(), storageType)
}
var remoteService remoteMetadataService
if url.Protocol == constant.TriProtocol &&
instance.GetMetadata()[constant.MetadataVersion] ==
constant.MetadataServiceV2Version {
@@ -86,7 +94,12 @@ func GetMetadataFromRpcWithContext(ctx context.Context,
revision string, instanc
defer func() {
invoker.Destroy()
}()
- return remoteService.getMetadataInfo(ctx, revision)
+ metadataInfo, err := remoteService.getMetadataInfo(ctx, revision)
+ if err != nil {
+ return metadataInfo, fmt.Errorf("rpc_metadata failed: app=%s
revision=%s instance_id=%s host=%s storage_type=%s: %w",
+ instance.GetServiceName(), revision, instance.GetID(),
instance.GetHost(), storageType, err)
+ }
+ return metadataInfo, nil
}
// remoteMetadataService is the internal interface for fetching MetadataInfo
via RPC.
@@ -287,7 +300,8 @@ func getMetadataServiceUrlParams(ins
registry.ServiceInstance) map[string]string
if str, ok := ps[constant.MetadataServiceURLParamsPropertyName]; ok &&
len(str) > 0 {
err := json.Unmarshal([]byte(str), &res)
if err != nil {
- logger.Errorf("[Metadata][URL] could not parse the
metadata service url parameters to map, err=%v", err)
+ logger.Errorf("[Metadata][URL] url_construction failed:
app=%s instance_id=%s host=%s: could not parse metadata service URL parameters:
%v",
+ ins.GetServiceName(), ins.GetID(),
ins.GetHost(), err)
}
}
diff --git a/metadata/client_test.go b/metadata/client_test.go
index c33413438..1c1536823 100644
--- a/metadata/client_test.go
+++ b/metadata/client_test.go
@@ -91,6 +91,20 @@ func TestGetMetadataFromMetadataReport(t *testing.T) {
instances = make(map[string]report.MetadataReport)
_, err := GetMetadataFromMetadataReport("1", ins, "default")
require.Error(t, err)
+ assert.Contains(t, err.Error(), "metadata_report failed:")
+ assert.Contains(t, err.Error(), "operation=get")
+ assert.Contains(t, err.Error(), "app=dubbo-app")
+ assert.Contains(t, err.Error(), "revision=1")
+ assert.Contains(t, err.Error(), "registry_id=default")
+ assert.Contains(t, err.Error(), "storage_type=remote")
+ })
+
+ t.Run("no report instance with empty registry id", func(t *testing.T) {
+ instances = make(map[string]report.MetadataReport)
+ _, err := GetMetadataFromMetadataReport("1", ins, "")
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "metadata_report failed:")
+ assert.Contains(t, err.Error(), "registry_id=")
})
t.Run("default registry routes to default report", func(t *testing.T) {
@@ -139,11 +153,19 @@ func TestGetMetadataFromMetadataReport(t *testing.T) {
instances = make(map[string]report.MetadataReport)
mockReport := new(mockMetadataReport)
defer mockReport.AssertExpectations(t)
- instances["default"] = mockReport
+ instances["default"] = &DelegateMetadataReport{instance:
mockReport}
- mockReport.On("GetAppMetadata").Return(metadataInfo,
errors.New("mock error")).Once()
+ sourceErr := errors.New("mock error")
+ mockReport.On("GetAppMetadata").Return(metadataInfo,
sourceErr).Once()
_, err := GetMetadataFromMetadataReport("1", ins, "default")
require.Error(t, err)
+ assert.Contains(t, err.Error(), "metadata_report failed:")
+ assert.Contains(t, err.Error(), "operation=get")
+ assert.Contains(t, err.Error(), "app=dubbo-app")
+ assert.Contains(t, err.Error(), "revision=1")
+ assert.Contains(t, err.Error(), "registry_id=default")
+ assert.Contains(t, err.Error(), "storage_type=remote")
+ require.ErrorIs(t, err, sourceErr)
})
}
@@ -173,17 +195,31 @@ func TestGetMetadataFromRpc(t *testing.T) {
mockProtocol.On("Refer").Return(nil).Once()
_, err := GetMetadataFromRpc("111", ins)
require.Error(t, err)
+ assert.Contains(t, err.Error(), "rpc_metadata failed:")
+ assert.Contains(t, err.Error(), "app=dubbo-app")
+ assert.Contains(t, err.Error(), "revision=111")
+ assert.Contains(t, err.Error(), "instance_id=1")
+ assert.Contains(t, err.Error(), "host=dubbo.io")
+ assert.Contains(t, err.Error(), "storage_type=local")
})
t.Run("invoke timeout", func(t *testing.T) {
+ sourceErr := errors.New("timeout error")
mockProtocol.On("Refer").Return(mockInvoker).Once()
mockInvoker.On("Invoke").Return(&result.RPCResult{
Attrs: map[string]any{},
- Err: errors.New("timeout error"),
+ Err: sourceErr,
Rest: metadataInfo,
}).Once()
mockInvoker.On("Destroy").Once()
_, err := GetMetadataFromRpc("111", ins)
require.Error(t, err)
+ assert.Contains(t, err.Error(), "rpc_metadata failed:")
+ assert.Contains(t, err.Error(), "app=dubbo-app")
+ assert.Contains(t, err.Error(), "revision=111")
+ assert.Contains(t, err.Error(), "instance_id=1")
+ assert.Contains(t, err.Error(), "host=dubbo.io")
+ assert.Contains(t, err.Error(), "storage_type=local")
+ require.ErrorIs(t, err, sourceErr)
})
}
@@ -208,6 +244,21 @@ func TestGetMetadataFromRpcWithContext(t *testing.T) {
assert.Same(t, ctx, mockInvoker.invokedContext)
}
+func TestGetMetadataFromRpcWithCanceledContext(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+
+ _, err := GetMetadataFromRpcWithContext(ctx, "111", ins)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "rpc_metadata failed:")
+ assert.Contains(t, err.Error(), "app=dubbo-app")
+ assert.Contains(t, err.Error(), "revision=111")
+ assert.Contains(t, err.Error(), "instance_id=1")
+ assert.Contains(t, err.Error(), "host=dubbo.io")
+ assert.Contains(t, err.Error(), "storage_type=local")
+ require.ErrorIs(t, err, context.Canceled)
+}
+
func TestTriMetadataServiceWithContext(t *testing.T) {
mockInvoker := new(mockInvoker)
mockInvoker.url =
common.NewURLWithOptions(common.WithProtocol(constant.TriProtocol))
@@ -230,6 +281,12 @@ func TestGetMetadataFromRpc_MissingURLParams(t *testing.T)
{
}
_, err := GetMetadataFromRpc("1", insNoProto)
require.Error(t, err)
+ assert.Contains(t, err.Error(), "url_construction failed:")
+ assert.Contains(t, err.Error(), "app=dubbo-app")
+ assert.Contains(t, err.Error(), "revision=1")
+ assert.Contains(t, err.Error(), "instance_id=2")
+ assert.Contains(t, err.Error(), "host=dubbo.io")
+ assert.Contains(t, err.Error(), "storage_type=local")
assert.Contains(t, err.Error(), "protocol is empty")
})
@@ -244,6 +301,12 @@ func TestGetMetadataFromRpc_MissingURLParams(t *testing.T)
{
}
_, err := GetMetadataFromRpc("1", insNoPort)
require.Error(t, err)
+ assert.Contains(t, err.Error(), "url_construction failed:")
+ assert.Contains(t, err.Error(), "app=dubbo-app")
+ assert.Contains(t, err.Error(), "revision=1")
+ assert.Contains(t, err.Error(), "instance_id=3")
+ assert.Contains(t, err.Error(), "host=dubbo.io")
+ assert.Contains(t, err.Error(), "storage_type=local")
assert.Contains(t, err.Error(), "port is empty")
})
}
diff --git a/metadata/mapping/metadata/service_name_mapping.go
b/metadata/mapping/metadata/service_name_mapping.go
index 4202e9dd5..09d030728 100644
--- a/metadata/mapping/metadata/service_name_mapping.go
+++ b/metadata/mapping/metadata/service_name_mapping.go
@@ -19,6 +19,7 @@ package metadata
import (
"errors"
+ "fmt"
"sync"
"time"
)
@@ -74,6 +75,7 @@ type ServiceNameMapping struct {
// Map will map the service to this application-level service
func (d *ServiceNameMapping) Map(url *common.URL) (err error) {
serviceInterface := url.GetParam(constant.InterfaceKey, "")
+ serviceKey := url.ServiceKey()
appName := url.GetParam(constant.ApplicationKey, "")
event :=
metadataMetrics.NewMetadataMetricTimeEvent(metadataMetrics.MetadataMappingRegister)
@@ -90,13 +92,20 @@ func (d *ServiceNameMapping) Map(url *common.URL) (err
error) {
// if the mapping can hold a report instance, it can write once
metadataReports := metadata.GetMetadataReports()
if len(metadataReports) == 0 {
- err = errors.New("can not registering mapping to remote cause
no metadata report instance found")
+ err = fmt.Errorf("mapping_register failed: service_key=%s
interface=%s application=%s group=%s reports=0: no metadata report instance
found",
+ serviceKey, serviceInterface, appName, DefaultGroup)
logger.Errorf("[Metadata][Mapping] register failed interface=%s
application=%s group=%s reports=0 err=%v", serviceInterface, appName,
DefaultGroup, err)
return err
}
for _, metadataReport := range metadataReports {
- if err := registerWithRetry(metadataReport, serviceInterface,
DefaultGroup, appName); err != nil {
+ if registerErr := registerWithRetry(metadataReport,
serviceInterface, DefaultGroup, appName); registerErr != nil {
+ reportURL := ""
+ if u := metadataReport.URL(); u != nil {
+ reportURL = u.Protocol + "://" + u.Address()
+ }
+ err = fmt.Errorf("mapping_register failed:
service_key=%s interface=%s application=%s group=%s reports=%d report_url=%s:
%w",
+ serviceKey, serviceInterface, appName,
DefaultGroup, len(metadataReports), reportURL, registerErr)
logger.Errorf("[Metadata][Mapping] register failed
interface=%s application=%s group=%s reports=%d err=%v", serviceInterface,
appName, DefaultGroup, len(metadataReports), err)
return err
}
@@ -136,11 +145,14 @@ func backoff(attempt int) time.Duration {
// Get will return the application-level services. If not found, the empty set
will be returned.
func (d *ServiceNameMapping) Get(url *common.URL, listener
mapping.MappingListener) (result *gxset.HashSet, err error) {
serviceInterface := url.GetParam(constant.InterfaceKey, "")
+ serviceKey := url.ServiceKey()
operation := "get"
+ errorCategory := "mapping_get"
eventName := metadataMetrics.MetadataMappingGet
if listener != nil {
operation = "listen"
+ errorCategory = "mapping_listen"
eventName = metadataMetrics.MetadataMappingListen
}
@@ -157,7 +169,8 @@ func (d *ServiceNameMapping) Get(url *common.URL, listener
mapping.MappingListen
metadataReports := metadata.GetMetadataReports()
if len(metadataReports) == 0 {
- err = errors.New("can not get mapping in remote cause no
metadata report instance found")
+ err = fmt.Errorf("%s failed: service_key=%s interface=%s
group=%s reports=0: no metadata report instance found",
+ errorCategory, serviceKey, serviceInterface,
DefaultGroup)
logger.Warnf("[Metadata][Mapping] get failed interface=%s
group=%s reports=0 err=%v", serviceInterface, DefaultGroup, err)
return nil, err
}
@@ -174,11 +187,13 @@ func (d *ServiceNameMapping) Get(url *common.URL,
listener mapping.MappingListen
}
set, getErr :=
metadataReport.GetServiceAppMapping(serviceInterface, DefaultGroup,
reportListener)
if getErr != nil {
- errs = append(errs, getErr)
reportURL := ""
if u := metadataReport.URL(); u != nil {
reportURL = u.Protocol + "://" + u.Address()
}
+ getErr = fmt.Errorf("%s failed: service_key=%s
interface=%s group=%s report=%d/%d report_url=%s: %w",
+ errorCategory, serviceKey, serviceInterface,
DefaultGroup, i+1, len(metadataReports), reportURL, getErr)
+ errs = append(errs, getErr)
logger.Warnf("[Metadata][Mapping] %s report %d/%d
failed interface=%s group=%s url=%s err=%v", operation, i+1,
len(metadataReports), serviceInterface, DefaultGroup, reportURL, getErr)
continue
}
@@ -207,6 +222,7 @@ func (d *ServiceNameMapping) Get(url *common.URL, listener
mapping.MappingListen
// error in one of the others.
func (d *ServiceNameMapping) Remove(url *common.URL) (err error) {
serviceInterface := url.GetParam(constant.InterfaceKey, "")
+ serviceKey := url.ServiceKey()
event :=
metadataMetrics.NewMetadataMetricTimeEvent(metadataMetrics.MetadataMappingRemove)
event.Attachment[constant.InterfaceKey] = serviceInterface
@@ -219,14 +235,21 @@ func (d *ServiceNameMapping) Remove(url *common.URL) (err
error) {
metadataReports := metadata.GetMetadataReports()
if len(metadataReports) == 0 {
- err = errors.New("can not remove mapping in remote cause no
metadata report instance found")
+ err = fmt.Errorf("mapping_remove failed: service_key=%s
interface=%s group=%s reports=0: no metadata report instance found",
+ serviceKey, serviceInterface, DefaultGroup)
logger.Warnf("[Metadata][Mapping] remove failed interface=%s
group=%s reports=0 err=%v", serviceInterface, DefaultGroup, err)
return err
}
var errs []error
- for _, metadataReport := range metadataReports {
+ for i, metadataReport := range metadataReports {
if removeErr :=
metadataReport.RemoveServiceAppMappingListener(serviceInterface, DefaultGroup);
removeErr != nil {
+ reportURL := ""
+ if u := metadataReport.URL(); u != nil {
+ reportURL = u.Protocol + "://" + u.Address()
+ }
+ removeErr = fmt.Errorf("mapping_remove failed:
service_key=%s interface=%s group=%s report=%d/%d report_url=%s: %w",
+ serviceKey, serviceInterface, DefaultGroup,
i+1, len(metadataReports), reportURL, removeErr)
errs = append(errs, removeErr)
}
}
diff --git a/metadata/mapping/metadata/service_name_mapping_test.go
b/metadata/mapping/metadata/service_name_mapping_test.go
index 7c5b384d7..882b4a23b 100644
--- a/metadata/mapping/metadata/service_name_mapping_test.go
+++ b/metadata/mapping/metadata/service_name_mapping_test.go
@@ -61,10 +61,17 @@ func TestNoReportInstance(t *testing.T) {
)
_, err := ins.Get(serviceUrl, lis)
require.Error(t, err, "test Get no report instance")
+ assert.Contains(t, err.Error(), "mapping_listen failed:")
+ assert.Contains(t, err.Error(),
"service_key=org.apache.dubbo.samples.proto.GreetService")
+ assert.Contains(t, err.Error(),
"interface=org.apache.dubbo.samples.proto.GreetService")
+ assert.Contains(t, err.Error(), "group=mapping")
err = ins.Map(serviceUrl)
require.Error(t, err, "test Map with no report instance")
+ assert.Contains(t, err.Error(), "mapping_register failed:")
+ assert.Contains(t, err.Error(), "application=dubbo")
err = ins.Remove(serviceUrl)
require.Error(t, err, "test Remove with no report instance")
+ assert.Contains(t, err.Error(), "mapping_remove failed:")
}
func TestServiceNameMappingNoReportMetersPerBusinessOperation(t *testing.T) {
@@ -85,6 +92,7 @@ func
TestServiceNameMappingNoReportMetersPerBusinessOperation(t *testing.T) {
err := ins.Map(serviceUrl)
require.Error(t, err)
+ assert.Contains(t, err.Error(), "mapping_register failed:")
wantAttachment := mappingAttachment("org.example.NoReportService")
wantAttachment[constant.ApplicationKey] = "no-report-app"
assertMappingMetricEvent(t, <-ch,
metricsMetadata.MetadataMappingRegister, false, false, wantAttachment)
@@ -92,16 +100,19 @@ func
TestServiceNameMappingNoReportMetersPerBusinessOperation(t *testing.T) {
_, err = ins.Get(serviceUrl, nil)
require.Error(t, err)
+ assert.Contains(t, err.Error(), "mapping_get failed:")
assertMappingMetricEvent(t, <-ch, metricsMetadata.MetadataMappingGet,
false, false, mappingAttachment("org.example.NoReportService"))
assert.Empty(t, ch)
_, err = ins.Get(serviceUrl, &listener{})
require.Error(t, err)
+ assert.Contains(t, err.Error(), "mapping_listen failed:")
assertMappingMetricEvent(t, <-ch,
metricsMetadata.MetadataMappingListen, false, false,
mappingAttachment("org.example.NoReportService"))
assert.Empty(t, ch)
err = ins.Remove(serviceUrl)
require.Error(t, err)
+ assert.Contains(t, err.Error(), "mapping_remove failed:")
assertMappingMetricEvent(t, <-ch,
metricsMetadata.MetadataMappingRemove, false, false,
mappingAttachment("org.example.NoReportService"))
assert.Empty(t, ch)
}
@@ -122,9 +133,16 @@ func TestServiceNameMappingGet(t *testing.T) {
assert.False(t, apps.Empty())
})
t.Run("test error", func(t *testing.T) {
- mockReport.On("GetServiceAppMapping").Return(gxset.NewSet(),
errors.New("mock error")).Once()
+ sourceErr := errors.New("mock error")
+ mockReport.On("GetServiceAppMapping").Return(gxset.NewSet(),
sourceErr).Once()
_, err = ins.Get(serviceUrl, lis)
require.Error(t, err)
+ assert.Contains(t, err.Error(), "mapping_listen failed:")
+ assert.Contains(t, err.Error(),
"service_key=org.apache.dubbo.samples.proto.GreetService")
+ assert.Contains(t, err.Error(),
"interface=org.apache.dubbo.samples.proto.GreetService")
+ assert.Contains(t, err.Error(), "group=mapping")
+ assert.Contains(t, err.Error(), "report_url=mock://127.0.0.1")
+ require.ErrorIs(t, err, sourceErr)
})
mockReport.AssertExpectations(t)
}
@@ -144,9 +162,15 @@ func TestServiceNameMappingMap(t *testing.T) {
})
t.Run("non-conflict error returns immediately", func(t *testing.T) {
// a generic error is not retriable, so
RegisterServiceAppMapping is called exactly once
-
mockReport.On("RegisterServiceAppMapping").Return(errors.New("mock
error")).Once()
+ sourceErr := errors.New("mock error")
+
mockReport.On("RegisterServiceAppMapping").Return(sourceErr).Once()
err = ins.Map(serviceUrl)
require.Error(t, err, "test mapping error")
+ assert.Contains(t, err.Error(), "mapping_register failed:")
+ assert.Contains(t, err.Error(),
"service_key=org.apache.dubbo.samples.proto.GreetService")
+ assert.Contains(t, err.Error(), "application=dubbo")
+ assert.Contains(t, err.Error(), "report_url=mock://127.0.0.1")
+ require.ErrorIs(t, err, sourceErr)
})
t.Run("CAS conflict retries up to retryTimes", func(t *testing.T) {
const conflictRetries = 3
@@ -154,6 +178,8 @@ func TestServiceNameMappingMap(t *testing.T) {
mockReport.On("RegisterServiceAppMapping").Return(report.ErrMappingCASConflict).Times(conflictRetries)
err = ins.Map(serviceUrl)
require.Error(t, err, "conflict exhausts the retry budget")
+ assert.Contains(t, err.Error(), "mapping_register failed:")
+ require.ErrorIs(t, err, report.ErrMappingCASConflict)
})
mockReport.AssertExpectations(t)
}
@@ -172,9 +198,14 @@ func TestServiceNameMappingRemove(t *testing.T) {
require.NoError(t, err)
})
t.Run("test error", func(t *testing.T) {
-
mockReport.On("RemoveServiceAppMappingListener").Return(errors.New("mock
error")).Once()
+ sourceErr := errors.New("mock error")
+
mockReport.On("RemoveServiceAppMappingListener").Return(sourceErr).Once()
err = ins.Remove(serviceUrl)
require.Error(t, err)
+ assert.Contains(t, err.Error(), "mapping_remove failed:")
+ assert.Contains(t, err.Error(),
"service_key=org.apache.dubbo.samples.proto.GreetService")
+ assert.Contains(t, err.Error(), "report_url=mock://127.0.0.1")
+ require.ErrorIs(t, err, sourceErr)
})
mockReport.AssertExpectations(t)
}
@@ -255,6 +286,9 @@ func TestServiceNameMappingRemoveCollectsAllErrors(t
*testing.T) {
err := ins.Remove(serviceUrl)
require.Error(t, err)
+ assert.Contains(t, err.Error(), "mapping_remove failed:")
+ assert.Contains(t, err.Error(), "service_key=org.example.BarService")
+ assert.Contains(t, err.Error(), "report_url=mock://127.0.0.1:8848")
// both individual errors must be present in the returned error
require.ErrorIs(t, err, err1)
require.ErrorIs(t, err, err2)
@@ -343,6 +377,8 @@ func TestServiceNameMappingGetMetersPerBusinessOperation(t
*testing.T) {
r2.On("GetServiceAppMapping").Return(gxset.NewSet(), errors.New("r2
failure")).Once()
_, err = ins.Get(serviceUrl, &listener{})
require.Error(t, err)
+ assert.Contains(t, err.Error(), "mapping_listen failed:")
+ require.ErrorIs(t, err, getErr)
assertMappingMetricEvent(t, <-ch,
metricsMetadata.MetadataMappingListen, false, false,
mappingAttachment("org.example.MeteredService"))
assert.Empty(t, ch)
diff --git a/metadata/report_instance.go b/metadata/report_instance.go
index 11dffa07b..19e973e6e 100644
--- a/metadata/report_instance.go
+++ b/metadata/report_instance.go
@@ -18,6 +18,7 @@
package metadata
import (
+ "fmt"
"sort"
"sync"
"time"
@@ -146,6 +147,10 @@ func (d *DelegateMetadataReport)
PublishAppMetadata(application, revision string
event.Succ = err == nil
event.End = time.Now()
metrics.Publish(event)
+ if err != nil {
+ return fmt.Errorf("metadata_report failed: operation=publish
app=%s revision=%s storage_type=%s: %w",
+ application, revision,
constant.RemoteMetadataStorageType, err)
+ }
return err
}
@@ -156,6 +161,10 @@ func (d *DelegateMetadataReport)
GetAppMetadata(application, revision string) (*
event.Succ = err == nil
event.End = time.Now()
metrics.Publish(event)
+ if err != nil {
+ return meta, fmt.Errorf("metadata_report failed: operation=get
app=%s revision=%s storage_type=%s: %w",
+ application, revision,
constant.RemoteMetadataStorageType, err)
+ }
return meta, err
}
@@ -173,10 +182,20 @@ func (d *DelegateMetadataReport)
RemoveServiceAppMappingListener(interfaceName,
// UnPublishAppMetadata delegate unpublish metadata info
func (d *DelegateMetadataReport) UnPublishAppMetadata(application, revision
string) error {
- return d.instance.UnPublishAppMetadata(application, revision)
+ err := d.instance.UnPublishAppMetadata(application, revision)
+ if err != nil {
+ return fmt.Errorf("metadata_report failed: operation=unpublish
app=%s revision=%s storage_type=%s: %w",
+ application, revision,
constant.RemoteMetadataStorageType, err)
+ }
+ return nil
}
// ListAppRevisions delegate list app revisions
func (d *DelegateMetadataReport) ListAppRevisions(application string)
([]report.AppRevision, error) {
- return d.instance.ListAppRevisions(application)
+ revisions, err := d.instance.ListAppRevisions(application)
+ if err != nil {
+ return revisions, fmt.Errorf("metadata_report failed:
operation=list_revisions app=%s storage_type=%s: %w",
+ application, constant.RemoteMetadataStorageType, err)
+ }
+ return revisions, nil
}
diff --git a/metadata/report_instance_test.go b/metadata/report_instance_test.go
index 87f311d49..5724b72e0 100644
--- a/metadata/report_instance_test.go
+++ b/metadata/report_instance_test.go
@@ -66,9 +66,16 @@ func TestDelegateMetadataReportGetAppMetadata(t *testing.T) {
assert.True(t, event.Succ)
})
t.Run("error", func(t *testing.T) {
-
mockReport.On("GetAppMetadata").Return(info.NewAppMetadataInfo("dubbo"),
errors.New("mock error")).Once()
+ sourceErr := errors.New("mock error")
+
mockReport.On("GetAppMetadata").Return(info.NewAppMetadataInfo("dubbo"),
sourceErr).Once()
_, err := delegate.GetAppMetadata("dubbo", "1111")
require.Error(t, err)
+ assert.Contains(t, err.Error(), "metadata_report failed:")
+ assert.Contains(t, err.Error(), "operation=get")
+ assert.Contains(t, err.Error(), "app=dubbo")
+ assert.Contains(t, err.Error(), "revision=1111")
+ assert.Contains(t, err.Error(), "storage_type=remote")
+ require.ErrorIs(t, err, sourceErr)
assert.Len(t, ch, 1)
metricEvent := <-ch
assert.Equal(t, constant.MetricsMetadata, metricEvent.Type())
@@ -104,9 +111,16 @@ func TestDelegateMetadataReportPublishAppMetadata(t
*testing.T) {
assert.True(t, event.Succ)
})
t.Run("error", func(t *testing.T) {
- mockReport.On("PublishAppMetadata").Return(errors.New("mock
error")).Once()
+ sourceErr := errors.New("mock error")
+ mockReport.On("PublishAppMetadata").Return(sourceErr).Once()
err := delegate.PublishAppMetadata("application", "revision",
metadataInfo)
require.Error(t, err)
+ assert.Contains(t, err.Error(), "metadata_report failed:")
+ assert.Contains(t, err.Error(), "operation=publish")
+ assert.Contains(t, err.Error(), "app=application")
+ assert.Contains(t, err.Error(), "revision=revision")
+ assert.Contains(t, err.Error(), "storage_type=remote")
+ require.ErrorIs(t, err, sourceErr)
assert.Len(t, ch, 1)
metricEvent := <-ch
assert.Equal(t, constant.MetricsMetadata, metricEvent.Type())
@@ -189,9 +203,16 @@ func TestDelegateMetadataReportUnPublishAppMetadata(t
*testing.T) {
require.NoError(t, err)
})
t.Run("error", func(t *testing.T) {
- mockReport.On("UnPublishAppMetadata").Return(errors.New("mock
error")).Once()
+ sourceErr := errors.New("mock error")
+ mockReport.On("UnPublishAppMetadata").Return(sourceErr).Once()
err := delegate.UnPublishAppMetadata("application", "revision")
require.Error(t, err)
+ assert.Contains(t, err.Error(), "metadata_report failed:")
+ assert.Contains(t, err.Error(), "operation=unpublish")
+ assert.Contains(t, err.Error(), "app=application")
+ assert.Contains(t, err.Error(), "revision=revision")
+ assert.Contains(t, err.Error(), "storage_type=remote")
+ require.ErrorIs(t, err, sourceErr)
})
}
@@ -210,9 +231,15 @@ func TestDelegateMetadataReportListAppRevisions(t
*testing.T) {
assert.Equal(t, expected, got)
})
t.Run("error", func(t *testing.T) {
-
mockReport.On("ListAppRevisions").Return([]report.AppRevision(nil),
errors.New("mock error")).Once()
+ sourceErr := errors.New("mock error")
+
mockReport.On("ListAppRevisions").Return([]report.AppRevision(nil),
sourceErr).Once()
_, err := delegate.ListAppRevisions("application")
require.Error(t, err)
+ assert.Contains(t, err.Error(), "metadata_report failed:")
+ assert.Contains(t, err.Error(), "operation=list_revisions")
+ assert.Contains(t, err.Error(), "app=application")
+ assert.Contains(t, err.Error(), "storage_type=remote")
+ require.ErrorIs(t, err, sourceErr)
})
}
diff --git a/registry/servicediscovery/service_discovery_registry.go
b/registry/servicediscovery/service_discovery_registry.go
index dec7edabb..df41e5453 100644
--- a/registry/servicediscovery/service_discovery_registry.go
+++ b/registry/servicediscovery/service_discovery_registry.go
@@ -145,7 +145,8 @@ func (s *serviceDiscoveryRegistry) RegisterService() error {
if metadata.GetMetadataType() == constant.RemoteMetadataStorageType {
if s.metadataReport == nil {
- return errors.New("can not publish app metadata cause
report instance not found")
+ return fmt.Errorf("metadata_report failed:
operation=publish app=%s revision=%s registry_id=%s storage_type=%s: no
metadata report instance found",
+ metaInfo.App, metaInfo.Revision, registryId,
constant.RemoteMetadataStorageType)
}
if err := s.metadataReport.PublishAppMetadata(metaInfo.App,
metaInfo.Revision, metaInfo); err != nil {
return err
@@ -306,7 +307,8 @@ func (s *serviceDiscoveryRegistry)
syncExportedMetadataAfterUnregister(targetURL
}
if metadata.GetMetadataType() == constant.RemoteMetadataStorageType {
if s.metadataReport == nil {
- return errors.New("can not publish app metadata cause
report instance not found")
+ return fmt.Errorf("metadata_report failed:
operation=publish app=%s revision=%s registry_id=%s storage_type=%s: no
metadata report instance found",
+ metadataInfo.App, revision, registryId,
constant.RemoteMetadataStorageType)
}
if err := s.metadataReport.PublishAppMetadata(metadataInfo.App,
revision, metadataInfo); err != nil {
return err
diff --git a/registry/servicediscovery/service_discovery_registry_test.go
b/registry/servicediscovery/service_discovery_registry_test.go
index 608e9b29f..bc6740558 100644
--- a/registry/servicediscovery/service_discovery_registry_test.go
+++ b/registry/servicediscovery/service_discovery_registry_test.go
@@ -329,7 +329,13 @@ func
TestServiceDiscoveryRegistryRegisterNilReportReturnsError(t *testing.T) {
err = sdReg.RegisterService()
require.Error(t, err)
- assert.Contains(t, err.Error(), "report instance not found")
+ assert.Contains(t, err.Error(), "metadata_report failed:")
+ assert.Contains(t, err.Error(), "operation=publish")
+ assert.Contains(t, err.Error(), "app="+testApp)
+ assert.Contains(t, err.Error(), "revision=")
+ assert.Contains(t, err.Error(), "registry_id="+regID)
+ assert.Contains(t, err.Error(), "storage_type=remote")
+ assert.Contains(t, err.Error(), "no metadata report instance found")
assert.False(t, mockSD.registerCalled, "no instance should be
registered when the metadata report is nil")
}
diff --git
a/registry/servicediscovery/service_instances_changed_listener_impl.go
b/registry/servicediscovery/service_instances_changed_listener_impl.go
index 18599d1c4..2c69cf6f2 100644
--- a/registry/servicediscovery/service_instances_changed_listener_impl.go
+++ b/registry/servicediscovery/service_instances_changed_listener_impl.go
@@ -384,12 +384,14 @@ func GetMetadataInfoWithContext(ctx context.Context, app
string, instance regist
initCache(app)
})
cacheKey := metadataCacheKey(app, registryId, revision)
- if metadataInfo, ok := metaCache.Get(cacheKey); ok {
+ if cachedValue, ok := metaCache.Get(cacheKey); ok {
logger.Debugf("[Metadata][Cache] app=%s registry=%s revision=%s
host=%s result=hit",
app, registryId, revision, instance.GetHost())
publishMetadataCacheEvent(app, true)
publishMetadataFetchEvent(app, metricsMetadata.SourceCache, "",
nil)
- return metadataInfo.(*info.MetadataInfo), nil
+ metadataInfo := cachedValue.(*info.MetadataInfo)
+ logMetadataRevisionMismatch(metadataInfo,
metricsMetadata.SourceCache, app, revision, registryId, instance)
+ return metadataInfo, nil
}
logger.Debugf("[Metadata][Cache] app=%s registry=%s revision=%s host=%s
result=miss",
app, registryId, revision, instance.GetHost())
@@ -414,11 +416,24 @@ func GetMetadataInfoWithContext(ctx context.Context, app
string, instance regist
publishMetadataFetchEvent(app, source, metricStorageType, err)
return nil, err
}
+ logMetadataRevisionMismatch(metadataInfo, source, app, revision,
registryId, instance)
metaCache.Set(cacheKey, metadataInfo)
publishMetadataFetchEvent(app, source, metricStorageType, nil)
return metadataInfo, nil
}
+func logMetadataRevisionMismatch(metadataInfo *info.MetadataInfo, source, app,
expectedRevision, registryId string, instance registry.ServiceInstance) {
+ if metadataInfo == nil || metadataInfo.Revision == expectedRevision {
+ return
+ }
+ storageType := constant.DefaultMetadataStorageType
+ if instanceMetadata := instance.GetMetadata(); instanceMetadata != nil
&& instanceMetadata[constant.MetadataStorageTypePropertyName] != "" {
+ storageType =
instanceMetadata[constant.MetadataStorageTypePropertyName]
+ }
+ logger.Warnf("[Metadata] revision_mismatch failed: source=%s app=%s
expected_revision=%q actual_revision=%q registry_id=%s storage_type=%s
instance_id=%s host=%s",
+ source, app, expectedRevision, metadataInfo.Revision,
registryId, storageType, instance.GetID(), instance.GetHost())
+}
+
func publishMetadataCacheEvent(app string, hit bool) {
event :=
metricsMetadata.NewMetadataMetricTimeEvent(metricsMetadata.MetadataCache)
event.Succ = hit
@@ -566,20 +581,25 @@ func getRemoteMetadataInfo(ctx context.Context, app
string, instance registry.Se
metadataInfo, rpcErr := metadata.GetMetadataFromRpcWithContext(ctx,
revision, instance)
if rpcErr != nil {
- return nil, metricsMetadata.SourceRpc,
wrapMetadataRPCFallbackError(rpcErr, reportErr)
+ rpcErr = wrapMetadataRPCFallbackError(rpcErr, reportErr)
+ rpcErr = fmt.Errorf("%w; registry_id=%s", rpcErr, registryId)
+ return nil, metricsMetadata.SourceRpc, rpcErr
}
metadataInfo, rpcErr = requireMetadataInfo(metadataInfo, app,
registryId, revision)
+ if rpcErr != nil {
+ return nil, metricsMetadata.SourceRpc,
wrapMetadataRPCFallbackError(rpcErr, reportErr)
+ }
return metadataInfo, metricsMetadata.SourceRpc, rpcErr
}
func logMetadataReportFallback(app, registryId, revision string, reportErr
error) {
if reportErr != nil {
- logger.Errorf("[Metadata][Fallback] report failed, fallback to
RPC app=%s registry=%s revision=%s err=%v",
- app, registryId, revision, reportErr)
+ logger.Errorf("[Metadata][Fallback] report failed, fallback to
RPC app=%s registry=%s revision=%s storage_type=%s err=%v",
+ app, registryId, revision,
constant.RemoteMetadataStorageType, reportErr)
return
}
- logger.Warnf("[Metadata][Fallback] report returned nil metadata,
fallback to RPC app=%s registry=%s revision=%s",
- app, registryId, revision)
+ logger.Warnf("[Metadata][Fallback] report returned nil metadata,
fallback to RPC app=%s registry=%s revision=%s storage_type=%s",
+ app, registryId, revision, constant.RemoteMetadataStorageType)
}
func wrapMetadataRPCFallbackError(rpcErr, reportErr error) error {
@@ -593,8 +613,8 @@ func wrapMetadataRPCFallbackError(rpcErr, reportErr error)
error {
func requireMetadataInfo(metadataInfo *info.MetadataInfo, app, registryId,
revision string) (*info.MetadataInfo, error) {
if metadataInfo == nil {
- return nil, fmt.Errorf("got nil metadata from RPC app=%s
registry=%s revision=%s",
- app, registryId, revision)
+ return nil, fmt.Errorf("rpc_metadata failed: app=%s revision=%s
registry_id=%s: metadata is nil",
+ app, revision, registryId)
}
return metadataInfo, nil
}
diff --git
a/registry/servicediscovery/service_instances_changed_listener_impl_test.go
b/registry/servicediscovery/service_instances_changed_listener_impl_test.go
index 232f6b4dc..ef88f9e1c 100644
--- a/registry/servicediscovery/service_instances_changed_listener_impl_test.go
+++ b/registry/servicediscovery/service_instances_changed_listener_impl_test.go
@@ -524,6 +524,11 @@ func TestGetMetadataInfo_LocalStorageGoesDirectlyToRPC(t
*testing.T) {
require.Error(t, err)
// Must be a URL/RPC error, not a report error, confirming the local
path
// skips the report entirely and goes straight to RPC.
+ assert.Contains(t, err.Error(), "url_construction failed:")
+ assert.Contains(t, err.Error(), "app=test-app")
+ assert.Contains(t, err.Error(), "revision=rev-local-rpc")
+ assert.Contains(t, err.Error(), "registry=default")
+ assert.Contains(t, err.Error(), "storage_type=local")
assert.Contains(t, err.Error(), "metadata service URL params missing",
"local storage path should go directly to RPC, not touch the
metadata report")
}
@@ -552,8 +557,13 @@ func TestGetMetadataInfo_FallbackToRPC(t *testing.T) {
require.Error(t, err)
// Both report and RPC fail: the combined error proves the fallback
path was taken
// and includes the RPC/URL failure as the wrapped cause.
- assert.Contains(t, err.Error(), "both paths failed",
- "fallback path should produce a combined error mentioning both
failures")
+ assert.Contains(t, err.Error(), "url_construction failed:")
+ assert.Contains(t, err.Error(), "both paths failed, reportErr:
metadata_report failed:",
+ "fallback path should retain the report failure")
+ assert.Contains(t, err.Error(), "app=test-app")
+ assert.Contains(t, err.Error(), "revision=rev-fallback-to-rpc")
+ assert.Contains(t, err.Error(), "registry_id=default")
+ assert.Contains(t, err.Error(), "storage_type=remote")
assert.Contains(t, err.Error(), "metadata service URL params missing",
"fallback error should include the RPC/URL failure cause")
}
@@ -607,8 +617,11 @@ func TestGetMetadataInfo_ReportReturnsNil_FallsBackToRPC(t
*testing.T) {
require.Error(t, err)
// The report returned nil (no error), so the fallback was triggered
and then RPC
// failed at URL construction. The error must reflect the
RPC-after-nil-report path.
+ assert.Contains(t, err.Error(), "url_construction failed:")
assert.Contains(t, err.Error(), "RPC fallback failed after report
returned nil metadata",
"nil report result should trigger fallback and surface an RPC
error")
+ assert.Contains(t, err.Error(), "registry_id="+regID)
+ assert.Contains(t, err.Error(), "storage_type=remote")
assert.Contains(t, err.Error(), "metadata service URL params missing",
"fallback error should include the RPC/URL failure cause")
mockReport.AssertExpectations(t)