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

baerwang pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go-pixiu.git


The following commit(s) were added to refs/heads/develop by this push:
     new b39ccaad feat: added support for configcenter listening and logger 
hot-reloading (#647)
b39ccaad is described below

commit b39ccaadd212812a2f3fcf76d84148c46e635c46
Author: mutezebra <[email protected]>
AuthorDate: Mon Jan 20 14:20:30 2025 +0800

    feat: added support for configcenter listening and logger hot-reloading 
(#647)
    
    * feat: added support for configcenter listening and logger hot-reloading
    
    * fix: fix license
    
    * style: changed the format of import
    
    * style: use go fmt
    
    * fix: fix code
    
    * style: add license
---
 configcenter/configclient.go                       |   9 +-
 configcenter/load.go                               |  28 +++--
 configcenter/load_test.go                          |   7 +-
 configcenter/nacos_load.go                         | 121 ++++++++++++---------
 configcenter/nacos_load_test.go                    | 110 +++++++++++++++++++
 configs/conf_with_nacos.yaml                       | 118 ++++++++++++++++++++
 go.mod                                             |  15 ++-
 go.sum                                             |  34 ++++--
 pkg/cmd/gateway.go                                 |   3 +
 .../common/constant/hotreload.go                   |  14 +--
 pkg/config/config_load.go                          |  13 ++-
 pkg/hotreload/hotreload.go                         | 113 +++++++++++++++++++
 pkg/hotreload/logger.go                            |  99 +++++++++++++++++
 pkg/logger/controller.go                           |   4 +-
 pkg/logger/logger.go                               |   5 +
 pkg/model/log.go                                   |   5 +-
 16 files changed, 607 insertions(+), 91 deletions(-)

diff --git a/configcenter/configclient.go b/configcenter/configclient.go
index 37475ce4..e74f5710 100644
--- a/configcenter/configclient.go
+++ b/configcenter/configclient.go
@@ -17,12 +17,17 @@
 
 package configcenter
 
+import (
+       "github.com/apache/dubbo-go-pixiu/pkg/model"
+)
+
 type (
        ConfigClient interface {
                LoadConfig(properties map[string]interface{}) (string, error)
 
                ListenConfig(properties map[string]interface{}) (err error)
-       }
 
-       ListenConfig func(data string)
+               // ViewConfig returns the current remote configuration.
+               ViewConfig() *model.Bootstrap
+       }
 )
diff --git a/configcenter/load.go b/configcenter/load.go
index ab579adc..f16289c3 100644
--- a/configcenter/load.go
+++ b/configcenter/load.go
@@ -22,7 +22,7 @@ import (
 )
 
 import (
-       "github.com/ghodss/yaml"
+       "gopkg.in/yaml.v3"
 )
 
 import (
@@ -42,6 +42,7 @@ var Parsers = map[string]func(data []byte, v interface{}) 
error{
 type (
        Load interface {
                LoadConfigs(boot *model.Bootstrap, opts ...Option) (v 
*model.Bootstrap, err error)
+               ViewRemoteConfig() *model.Bootstrap
        }
 
        Option func(opt *Options)
@@ -61,12 +62,14 @@ type DefaultConfigLoad struct {
 }
 
 func NewConfigLoad(bootConfig *model.Bootstrap) *DefaultConfigLoad {
-
        var configClient ConfigClient
-
+       var err error
        // config center load
        if strings.EqualFold(bootConfig.Config.Type, KEY_CONFIG_TYPE_NACOS) {
-               configClient, _ = NewNacosConfig(bootConfig)
+               configClient, err = NewNacosConfig(bootConfig)
+               if err != nil {
+                       logger.Errorf("Get new nacos config failed,err: %v", 
err)
+               }
        }
 
        if configClient == nil {
@@ -81,7 +84,6 @@ func NewConfigLoad(bootConfig *model.Bootstrap) 
*DefaultConfigLoad {
 }
 
 func (d *DefaultConfigLoad) LoadConfigs(boot *model.Bootstrap, opts ...Option) 
(v *model.Bootstrap, err error) {
-
        var opt Options
        for _, o := range opts {
                o(&opt)
@@ -104,7 +106,6 @@ func (d *DefaultConfigLoad) LoadConfigs(boot 
*model.Bootstrap, opts ...Option) (
        }
 
        data, err := d.configClient.LoadConfig(m)
-
        if err != nil {
                return nil, err
        }
@@ -114,11 +115,24 @@ func (d *DefaultConfigLoad) LoadConfigs(boot 
*model.Bootstrap, opts ...Option) (
                return boot, err
        }
 
-       err = Parsers[".yml"]([]byte(data), boot)
+       if err = Parsers[".yml"]([]byte(data), boot); err != nil {
+               logger.Errorf("failed to parse the configuration loaded from 
the remote,err: %v", err)
+               return boot, err
+       }
+
+       if err = d.configClient.ListenConfig(m); err != nil {
+               logger.Errorf("failed to listen the remote configcenter 
config,err: %v", err)
+               return boot, err
+       }
 
        return boot, err
 }
 
+// ViewRemoteConfig returns the current remote configuration.
+func (d *DefaultConfigLoad) ViewRemoteConfig() *model.Bootstrap {
+       return d.configClient.ViewConfig()
+}
+
 func ParseYamlBytes(content []byte, v interface{}) error {
        return yaml.Unmarshal(content, v)
 }
diff --git a/configcenter/load_test.go b/configcenter/load_test.go
index 292c1a8e..4fe85779 100644
--- a/configcenter/load_test.go
+++ b/configcenter/load_test.go
@@ -28,7 +28,6 @@ import (
 )
 
 func getBootstrap() *model.Bootstrap {
-
        return &model.Bootstrap{
                Config: &model.ConfigCenter{
                        Type:   "nacos",
@@ -69,7 +68,7 @@ func TestDefaultConfigLoad_LoadConfigs(t *testing.T) {
                boot *model.Bootstrap
                opts []Option
        }
-       var tests = []struct {
+       tests := []struct {
                name    string
                fields  fields
                args    args
@@ -139,8 +138,8 @@ func TestDefaultConfigLoad_LoadConfigs(t *testing.T) {
                                t.Errorf("LoadConfigs() error = %v, wantErr 
%v", err, tt.wantErr)
                                return
                        }
-                       //assert.True(t, gotV.Nacos.DataId == DataId, "load 
config by nacos config center error!")
-                       //assert.True(t, len(gotV.StaticResources.Listeners) > 
0, "load config by nacos config center error!")
+                       // assert.True(t, gotV.Nacos.DataId == DataId, "load 
config by nacos config center error!")
+                       // assert.True(t, len(gotV.StaticResources.Listeners) > 
0, "load config by nacos config center error!")
                        conf, _ := json.Marshal(gotV)
                        logger.Infof("config of Bootstrap load by nacos : %v", 
string(conf))
                })
diff --git a/configcenter/nacos_load.go b/configcenter/nacos_load.go
index 84681a8e..5f17857c 100644
--- a/configcenter/nacos_load.go
+++ b/configcenter/nacos_load.go
@@ -17,6 +17,10 @@
 
 package configcenter
 
+import (
+       "sync"
+)
+
 import (
        "github.com/nacos-group/nacos-sdk-go/clients"
        "github.com/nacos-group/nacos-sdk-go/clients/config_client"
@@ -30,6 +34,7 @@ import (
        "github.com/apache/dubbo-go-pixiu/pkg/model"
 )
 
+// Constants for configuration keys.
 const (
        KeyDataId  = "dataId"
        KeyGroup   = "group"
@@ -39,6 +44,7 @@ const (
        KeyTenant  = "tenant"
 )
 
+// Constants for Nacos configuration.
 const (
        DataId    = "pixiu.yaml"
        Group     = "DEFAULT_GROUP"
@@ -50,29 +56,41 @@ const (
        Scheme      = "http"
 )
 
-type (
-       NacosConfig struct {
-               client config_client.IConfigClient
+// NacosConfig represents the Nacos configuration client and its state.
+type NacosConfig struct {
+       client       config_client.IConfigClient
+       remoteConfig *model.Bootstrap
+       mu           sync.Mutex
+}
 
-               // todo not support now
-               listenConfigCallback ListenConfig
+// NewNacosConfig creates a new NacosConfig instance.
+// It returns an error if no Nacos server is configured or if the client 
cannot be created.
+func NewNacosConfig(boot *model.Bootstrap) (ConfigClient, error) {
+       if len(boot.Nacos.ServerConfigs) == 0 {
+               return nil, errors.New("no Nacos server configured")
        }
-)
-
-func NewNacosConfig(boot *model.Bootstrap) (configClient ConfigClient, err 
error) {
 
-       var sc []constant.ServerConfig
-       if len(boot.Nacos.ServerConfigs) == 0 {
-               return nil, errors.New("no nacos server be setted")
+       nacosClient, err := getNacosConfigClient(boot)
+       if err != nil {
+               return nil, err
        }
-       for _, serveConfig := range boot.Nacos.ServerConfigs {
-               sc = append(sc, constant.ServerConfig{
-                       Port:   serveConfig.Port,
-                       IpAddr: serveConfig.IpAddr,
+
+       return &NacosConfig{
+               client: nacosClient,
+       }, nil
+}
+
+// getNacosConfigClient initializes and returns a Nacos config client.
+func getNacosConfigClient(boot *model.Bootstrap) (config_client.IConfigClient, 
error) {
+       var serverConfigs []constant.ServerConfig
+       for _, serverConfig := range boot.Nacos.ServerConfigs {
+               serverConfigs = append(serverConfigs, constant.ServerConfig{
+                       Port:   serverConfig.Port,
+                       IpAddr: serverConfig.IpAddr,
                })
        }
 
-       cc := constant.ClientConfig{
+       clientConfig := constant.ClientConfig{
                NamespaceId:         boot.Nacos.ClientConfig.NamespaceId,
                TimeoutMs:           boot.Nacos.ClientConfig.TimeoutMs,
                NotLoadCacheAtStart: 
boot.Nacos.ClientConfig.NotLoadCacheAtStart,
@@ -81,21 +99,15 @@ func NewNacosConfig(boot *model.Bootstrap) (configClient 
ConfigClient, err error
                LogLevel:            boot.Nacos.ClientConfig.LogLevel,
        }
 
-       pa := vo.NacosClientParam{
-               ClientConfig:  &cc,
-               ServerConfigs: sc,
-       }
-       nacos, err := clients.NewConfigClient(pa)
-       if err != nil {
-               return nil, err
-       }
-       configClient = &NacosConfig{
-               client: nacos,
+       clientParam := vo.NacosClientParam{
+               ClientConfig:  &clientConfig,
+               ServerConfigs: serverConfigs,
        }
 
-       return configClient, nil
+       return clients.NewConfigClient(clientParam)
 }
 
+// LoadConfig retrieves the configuration from Nacos based on the provided 
parameters.
 func (n *NacosConfig) LoadConfig(param map[string]interface{}) (string, error) 
{
        return n.client.GetConfig(vo.ConfigParam{
                DataId: getOrDefault(param[KeyDataId].(string), DataId),
@@ -103,34 +115,45 @@ func (n *NacosConfig) LoadConfig(param 
map[string]interface{}) (string, error) {
        })
 }
 
-func getOrDefault(target string, quiet string) string {
+// getOrDefault returns the target value if it is not empty; otherwise, it 
returns the fallback value.
+func getOrDefault(target, fallback string) string {
        if len(target) == 0 {
-               target = quiet
+               return fallback
        }
        return target
 }
 
-func (n *NacosConfig) ListenConfig(param map[string]interface{}) (err error) {
-       // todo noop, not support
-       if true {
-               return nil
-       }
-       listen := n.listen(getOrDefault(param[KeyDataId].(string), DataId), 
getOrDefault(param[KeyGroup].(string), Group))
-       return listen()
+// ListenConfig listens for configuration changes in Nacos.
+func (n *NacosConfig) ListenConfig(param map[string]interface{}) error {
+       return n.client.ListenConfig(vo.ConfigParam{
+               DataId:   getOrDefault(param[KeyDataId].(string), DataId),
+               Group:    getOrDefault(param[KeyGroup].(string), Group),
+               OnChange: n.onChange,
+       })
 }
 
-func (n *NacosConfig) listen(dataId, group string) func() error {
-       return func() error {
-               return n.client.ListenConfig(vo.ConfigParam{
-                       DataId: dataId,
-                       Group:  group,
-                       OnChange: func(namespace, group, dataId, data string) {
-                               if len(data) == 0 {
-                                       logger.Errorf("nacos listen callback 
data nil error ,  namespace : %s,group : %s , dataId : %s , data : %s")
-                                       return
-                               }
-                               n.listenConfigCallback(data)
-                       },
-               })
+// onChange is the callback function triggered when the configuration changes 
in Nacos.
+func (n *NacosConfig) onChange(namespace, group, dataId, data string) {
+       if len(data) == 0 {
+               logger.Errorf("Nacos listen callback data is nil. Namespace: 
%s, Group: %s, DataId: %s", namespace, group, dataId)
+               return
        }
+
+       n.mu.Lock()
+       defer n.mu.Unlock()
+
+       var boot model.Bootstrap
+       if err := Parsers[".yml"]([]byte(data), &boot); err != nil {
+               logger.Errorf("Failed to parse the configuration loaded from 
the remote. Error: %v", err)
+               return
+       }
+
+       n.remoteConfig = &boot
+}
+
+// ViewConfig returns the current remote configuration.
+func (n *NacosConfig) ViewConfig() *model.Bootstrap {
+       n.mu.Lock()
+       defer n.mu.Unlock()
+       return n.remoteConfig
 }
diff --git a/configcenter/nacos_load_test.go b/configcenter/nacos_load_test.go
new file mode 100644
index 00000000..84ba7a33
--- /dev/null
+++ b/configcenter/nacos_load_test.go
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package configcenter
+
+import (
+       "fmt"
+       "io"
+       "os"
+       "path"
+       "strings"
+       "testing"
+)
+
+import (
+       . "github.com/smartystreets/goconvey/convey"
+)
+
+import (
+       "github.com/apache/dubbo-go-pixiu/pkg/logger"
+)
+
+// isNacosRunning checks whether the Nacos server is running.
+// It returns true if Nacos is running, otherwise false.
+func isNacosRunning(t *testing.T) bool {
+       t.Helper()
+       _, err := getNacosConfigClient(getBootstrap())
+       return err == nil
+}
+
+// TestNewNacosConfig tests the creation of a new Nacos configuration.
+// If Nacos is not running, the test is skipped.
+func TestNewNacosConfig(t *testing.T) {
+       if !isNacosRunning(t) {
+               t.Skip("Nacos is not running, skipping the test.")
+               return
+       }
+
+       Convey("Test NewNacosConfig", t, func() {
+               cfg := getBootstrap()
+
+               // Test successful creation of NacosConfig.
+               _, err := NewNacosConfig(cfg)
+               So(err, ShouldBeNil)
+
+               // Test creation failure when Nacos server configurations are 
missing.
+               cfg.Nacos.ServerConfigs = nil
+               _, err = NewNacosConfig(cfg)
+               So(err, ShouldNotBeNil)
+       })
+}
+
+// TestNacosConfig_onChange tests the onChange method of NacosConfig.
+func TestNacosConfig_onChange(t *testing.T) {
+       Convey("TestNacosConfig_onChange", t, func() {
+               cfg := getBootstrap()
+               c, err := NewNacosConfig(cfg)
+               So(err, ShouldBeNil)
+
+               client, ok := c.(*NacosConfig)
+               So(ok, ShouldBeTrue)
+
+               // Verify the current working directory.
+               wd, err := os.Getwd()
+               So(err, ShouldBeNil)
+
+               paths := strings.Split(wd, "/")
+               So(paths[len(paths)-1], ShouldEqual, "configcenter")
+
+               // Open the configuration file for testing.
+               file, err := os.Open(fmt.Sprintf("/%s/configs/conf.yaml", 
path.Join(paths[:len(paths)-1]...)))
+               So(err, ShouldBeNil)
+               defer func() { So(file.Close(), ShouldBeNil) }()
+
+               conf, err := io.ReadAll(file)
+               So(err, ShouldBeNil)
+
+               Convey("Test onChange with valid input", func() {
+                       So(client.remoteConfig, ShouldBeNil)
+                       client.onChange(Namespace, Group, DataId, string(conf))
+                       So(client.remoteConfig, ShouldNotBeNil)
+               })
+
+               Convey("Test onChange with empty input", func() {
+                       // Suppress logs during this test.
+                       logger.SetLoggerLevel("fatal")
+
+                       client.remoteConfig = nil
+                       client.onChange(Namespace, Group, DataId, "")
+                       So(client.remoteConfig, ShouldBeNil)
+
+                       // Restore the logger level.
+                       logger.SetLoggerLevel("info")
+               })
+       })
+}
diff --git a/configs/conf_with_nacos.yaml b/configs/conf_with_nacos.yaml
new file mode 100644
index 00000000..46037e32
--- /dev/null
+++ b/configs/conf_with_nacos.yaml
@@ -0,0 +1,118 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+---
+static_resources:
+  listeners:
+    - name: "net/http"
+      protocol_type: "HTTP"
+      address:
+        socket_address:
+          address: "0.0.0.0"
+          port: 8888
+      filter_chains:
+        filters:
+          - name: dgp.filter.httpconnectionmanager
+            config:
+              route_config:
+                routes:
+                  - match:
+                      prefix: "/user"
+                    route:
+                      cluster: "user"
+                      cluster_not_found_response_code: 505
+              http_filters:
+                - name: dgp.filter.http.httpproxy
+                  config:
+                - name: dgp.filter.http.cors
+                  config:
+                    allow_origin:
+                      - api.dubbo.com
+                    allow_methods: ""
+                    allow_headers: ""
+                    expose_headers: ""
+                    max_age: ""
+                    allow_credentials: false
+      config:
+        idle_timeout: 5s
+        read_timeout: 5s
+        write_timeout: 5s
+  clusters:
+    - name: "user"
+      lb_policy: "lb"
+      endpoints:
+        - id: 1
+          socket_address:
+            address: 127.0.0.1
+            port: 1314
+  shutdown_config:
+    timeout: "60s"
+    step_timeout: "10s"
+    reject_policy: "immediacy"
+
+config-center:
+  type: "nacos"
+  enable: true
+
+nacos:
+  server_configs:
+    - ip_addr: "localhost"
+      port: 8848
+      scheme: "http"
+      contextPath: "/nacos"
+    - ip_addr: "localhost"
+      port: 8848
+      scheme: "http"
+      contextPath: "/nacos"
+  client-config:
+    cache_dir: "./.cache"
+    log_dir: "./.log"
+    not_load_cache_at_start: true
+    namespace_id: "dubbo-go-pixiu"
+  data-id: "pixiu.yaml"
+  group: "DEFAULT_GROUP"
+
+log:
+  level: "debug"
+  development: true
+  disableCaller: false
+  disableStacktrace: false
+  sampling:
+  encoding: "console"
+
+  # encoder
+  encoderConfig:
+    messageKey: "message"
+    levelKey: "level"
+    timeKey: "time"
+    nameKey: "logger"
+    callerKey: "caller"
+    stacktraceKey: "stacktrace"
+    lineEnding: ""
+    levelEncoder: "capitalColor"
+    timeEncoder: "iso8601"
+    durationEncoder: "seconds"
+    callerEncoder: "short"
+    nameEncoder: ""
+
+  outputPaths:
+    - "stderr"
+  errorOutputPaths:
+    - "stderr"
+  initialFields:
+
diff --git a/go.mod b/go.mod
index e02699fc..f526fb03 100644
--- a/go.mod
+++ b/go.mod
@@ -17,7 +17,6 @@ require (
        github.com/dubbogo/grpc-go v1.42.10
        github.com/dubbogo/triple v1.2.2-rc3
        github.com/envoyproxy/go-control-plane 
v0.11.1-0.20230524094728-9239064ad72f
-       github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
        github.com/go-errors/errors v1.0.1
        github.com/go-playground/assert/v2 v2.2.0
        github.com/goinggo/mapstructure v0.0.0-20140717182941-194205d9b4a9
@@ -31,6 +30,7 @@ require (
        github.com/pkg/errors v0.9.1
        github.com/prometheus/client_golang v1.14.0
        github.com/prometheus/common v0.37.0
+       github.com/smartystreets/goconvey v1.7.2
        github.com/spf13/cast v1.5.0
        github.com/spf13/cobra v1.5.0
        github.com/stretchr/testify v1.8.4
@@ -50,6 +50,7 @@ require (
        google.golang.org/grpc v1.56.3
        google.golang.org/protobuf v1.30.0
        gopkg.in/yaml.v2 v2.4.0
+       gopkg.in/yaml.v3 v3.0.1
        mosn.io/proxy-wasm-go-host v0.1.0
 )
 
@@ -89,6 +90,7 @@ require (
        github.com/gogo/protobuf v1.3.2 // indirect
        github.com/golang/snappy v0.0.4 // indirect
        github.com/google/uuid v1.3.0 // indirect
+       github.com/gopherjs/gopherjs v1.12.80 // indirect
        github.com/gorilla/websocket v1.4.2 // indirect
        github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect
        github.com/grpc-ecosystem/grpc-opentracing 
v0.0.0-20180507213350-8e809c8a8645 // indirect
@@ -107,6 +109,7 @@ require (
        github.com/jinzhu/copier v0.3.5 // indirect
        github.com/jmespath/go-jmespath v0.4.0 // indirect
        github.com/json-iterator/go v1.1.12 // indirect
+       github.com/jtolds/gls v4.20.0+incompatible // indirect
        github.com/k0kubun/pp v3.0.1+incompatible // indirect
        github.com/klauspost/compress v1.13.6 // indirect
        github.com/knadh/koanf v1.5.0 // indirect
@@ -137,6 +140,7 @@ require (
        github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // 
indirect
        github.com/robfig/cron/v3 v3.0.1 // indirect
        github.com/shirou/gopsutil/v3 v3.22.2 // indirect
+       github.com/smartystreets/assertions v1.2.0 // indirect
        github.com/spaolacci/murmur3 v1.1.0 // indirect
        github.com/spf13/pflag v1.0.5 // indirect
        github.com/tklauser/go-sysconf v0.3.10 // indirect
@@ -155,15 +159,14 @@ require (
        go.opentelemetry.io/proto/otlp v0.19.0 // indirect
        go.uber.org/atomic v1.10.0 // indirect
        go.uber.org/multierr v1.8.0 // indirect
-       golang.org/x/arch v0.0.0-20200826200359-b19915210f00 // indirect
+       golang.org/x/arch v0.11.0 // indirect
        golang.org/x/oauth2 v0.7.0 // indirect
-       golang.org/x/sync v0.1.0 // indirect
-       golang.org/x/sys v0.13.0 // indirect
-       golang.org/x/text v0.13.0 // indirect
+       golang.org/x/sync v0.7.0 // indirect
+       golang.org/x/sys v0.26.0 // indirect
+       golang.org/x/text v0.16.0 // indirect
        golang.org/x/time v0.1.0 // indirect
        google.golang.org/appengine v1.6.7 // indirect
        google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // 
indirect
        gopkg.in/ini.v1 v1.66.2 // indirect
        gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
-       gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index c1ac65ec..1bfae555 100644
--- a/go.sum
+++ b/go.sum
@@ -614,8 +614,6 @@ github.com/fsnotify/fsnotify v1.6.0 
h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
 github.com/fsnotify/fsnotify v1.6.0/go.mod 
h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
 github.com/getsentry/raven-go v0.2.0/go.mod 
h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/ghodss/yaml v1.0.0/go.mod 
h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 
h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
-github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod 
h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
 github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod 
h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
 github.com/go-co-op/gocron v1.9.0 
h1:+V+DDenw3ryB7B+tK1bAIC5p0ruw4oX9IqAsdRnGIf0=
 github.com/go-co-op/gocron v1.9.0/go.mod 
h1:DbJm9kdgr1sEvWpHCA7dFFs/PGHPMil9/97EXCRPr4k=
@@ -786,8 +784,9 @@ github.com/googleapis/gax-go/v2 v2.7.0/go.mod 
h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57Q
 github.com/googleapis/go-type-adapters v1.0.0/go.mod 
h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
 github.com/googleapis/google-cloud-go-testing 
v0.0.0-20200911160855-bcd43fbb19e8/go.mod 
h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod 
h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 
h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
 github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod 
h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v1.12.80 
h1:aC68NT6VK715WeUapxcPSFq/a3gZdS32HdtghdOIgAo=
+github.com/gopherjs/gopherjs v1.12.80/go.mod 
h1:d55Q4EjGQHeJVms+9LGtXul6ykz5Xzx1E1gaXQXdimY=
 github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod 
h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
 github.com/gorilla/context v1.1.1/go.mod 
h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/mux v1.6.2/go.mod 
h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -1055,6 +1054,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod 
h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
 github.com/nats-io/nkeys v0.1.0/go.mod 
h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nkeys v0.1.3/go.mod 
h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nuid v1.0.1/go.mod 
h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod 
h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
+github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod 
h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod 
h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod 
h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso=
 github.com/npillmayer/nestext v0.1.3/go.mod 
h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
@@ -1175,6 +1176,7 @@ github.com/robfig/cron/v3 v3.0.1 
h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 github.com/robfig/cron/v3 v3.0.1/go.mod 
h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod 
h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod 
h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.0.1-alpha.1/go.mod 
h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod 
h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1/go.mod 
h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.0 
h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
@@ -1191,18 +1193,23 @@ github.com/shirou/gopsutil v3.20.11+incompatible/go.mod 
h1:5b4v6he4MtMOwMlS0TUMT
 github.com/shirou/gopsutil/v3 v3.21.6/go.mod 
h1:JfVbDpIBLVzT8oKbvMg9P3wEIMDDpVn+LwHTKj0ST88=
 github.com/shirou/gopsutil/v3 v3.22.2 
h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks=
 github.com/shirou/gopsutil/v3 v3.22.2/go.mod 
h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
+github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod 
h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
+github.com/shurcooL/httpfs v0.0.0-20181222201310-74dc9339e414/go.mod 
h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod 
h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/shurcooL/vfsgen v0.0.0-20180915214035-33ae1944be3f/go.mod 
h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
 github.com/sirupsen/logrus v1.2.0/go.mod 
h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod 
h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod 
h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.7.0/go.mod 
h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.8.1 
h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod 
h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d 
h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod 
h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v1.2.0 
h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
+github.com/smartystreets/assertions v1.2.0/go.mod 
h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod 
h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/smartystreets/goconvey v1.6.4 
h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 github.com/smartystreets/goconvey v1.6.4/go.mod 
h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/smartystreets/goconvey v1.7.2 
h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
+github.com/smartystreets/goconvey v1.7.2/go.mod 
h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
 github.com/soheilhy/cmux v0.1.4/go.mod 
h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/soheilhy/cmux v0.1.5-0.20210205191134-5ec6847320e5 
h1:GJTW+uNMIV1RKwox+T4aN0/sQlYRg78uHZf2H0aBcDw=
 github.com/soheilhy/cmux v0.1.5-0.20210205191134-5ec6847320e5/go.mod 
h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
@@ -1382,8 +1389,10 @@ go.uber.org/zap v1.16.0/go.mod 
h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
 go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
 go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
-golang.org/x/arch v0.0.0-20200826200359-b19915210f00 
h1:cfd5G6xu8iZTFmjBYVemyBmE/sTf0A3vpE3BmoOuLCI=
 golang.org/x/arch v0.0.0-20200826200359-b19915210f00/go.mod 
h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
+golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
+golang.org/x/arch v0.11.0/go.mod 
h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/crypto v0.0.0-20180807104621-f027049dab0a/go.mod 
h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod 
h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod 
h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod 
h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -1565,8 +1574,10 @@ golang.org/x/sync 
v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20180807162357-acbc56fc7007/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1678,8 +1689,8 @@ golang.org/x/sys v0.2.0/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod 
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod 
h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@@ -1701,8 +1712,8 @@ golang.org/x/text v0.4.0/go.mod 
h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
-golang.org/x/text v0.13.0/go.mod 
h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod 
h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod 
h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod 
h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod 
h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1721,6 +1732,7 @@ golang.org/x/tools 
v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod 
h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190308142131-b40df0fb21c3/go.mod 
h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod 
h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod 
h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod 
h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
diff --git a/pkg/cmd/gateway.go b/pkg/cmd/gateway.go
index 9dc4ff69..4392d29b 100644
--- a/pkg/cmd/gateway.go
+++ b/pkg/cmd/gateway.go
@@ -32,6 +32,7 @@ import (
        "github.com/apache/dubbo-go-pixiu/pkg/common/constant"
        pxruntime "github.com/apache/dubbo-go-pixiu/pkg/common/runtime"
        "github.com/apache/dubbo-go-pixiu/pkg/config"
+       "github.com/apache/dubbo-go-pixiu/pkg/hotreload"
        "github.com/apache/dubbo-go-pixiu/pkg/logger"
        "github.com/apache/dubbo-go-pixiu/pkg/model"
        "github.com/apache/dubbo-go-pixiu/pkg/server"
@@ -125,6 +126,8 @@ func (d *DefaultDeployer) initialize() error {
                logger.Errorf("[startCmd] failed to get limit cpu number, %s", 
err.Error())
        }
 
+       hotreload.StartHotReload(d.configManger, d.bootstrap)
+
        return err
 }
 
diff --git a/configcenter/configclient.go b/pkg/common/constant/hotreload.go
similarity index 78%
copy from configcenter/configclient.go
copy to pkg/common/constant/hotreload.go
index 37475ce4..9d0d7072 100644
--- a/configcenter/configclient.go
+++ b/pkg/common/constant/hotreload.go
@@ -15,14 +15,12 @@
  * limitations under the License.
  */
 
-package configcenter
+package constant
 
-type (
-       ConfigClient interface {
-               LoadConfig(properties map[string]interface{}) (string, error)
-
-               ListenConfig(properties map[string]interface{}) (err error)
-       }
+import (
+       "time"
+)
 
-       ListenConfig func(data string)
+const (
+       CheckConfigInterval = 1 * time.Second
 )
diff --git a/pkg/config/config_load.go b/pkg/config/config_load.go
index a004a77d..25fcae13 100644
--- a/pkg/config/config_load.go
+++ b/pkg/config/config_load.go
@@ -27,9 +27,9 @@ import (
 
 import (
        "github.com/creasty/defaults"
-       "github.com/ghodss/yaml"
        "github.com/goinggo/mapstructure"
        "github.com/imdario/mergo"
+       "gopkg.in/yaml.v3"
 )
 
 import (
@@ -84,7 +84,7 @@ func CheckYamlFormat(path string) bool {
 
 // LoadYAMLConfig YAMLConfigLoad config load yaml
 func LoadYAMLConfig(path string) *model.Bootstrap {
-       log.Println("load config in YAML format from : ", path)
+       logger.Info("load config in YAML format from : ", path)
        content, err := os.ReadFile(path)
        if err != nil {
                log.Fatalln("[config] [yaml load] load config failed, ", err)
@@ -253,6 +253,15 @@ func (m *ConfigManager) loadRemoteBootConfigs() 
*model.Bootstrap {
        return configs
 }
 
+// ViewRemoteConfig returns the current remote configuration.
+func (m *ConfigManager) ViewRemoteConfig() *model.Bootstrap {
+       if m.load == nil {
+               return nil
+       }
+
+       return m.load.ViewRemoteConfig()
+}
+
 func (m *ConfigManager) check() error {
 
        return Adapter(config)
diff --git a/pkg/hotreload/hotreload.go b/pkg/hotreload/hotreload.go
new file mode 100644
index 00000000..cd6acce5
--- /dev/null
+++ b/pkg/hotreload/hotreload.go
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package hotreload
+
+import (
+       "sync"
+       "time"
+)
+
+import (
+       "github.com/apache/dubbo-go-pixiu/pkg/common/constant"
+       "github.com/apache/dubbo-go-pixiu/pkg/config"
+       "github.com/apache/dubbo-go-pixiu/pkg/logger"
+       "github.com/apache/dubbo-go-pixiu/pkg/model"
+)
+
+// HotReloader defines the interface for the HotReload module.
+type HotReloader interface {
+       // CheckUpdate checks if the configuration has changed and needs to be 
reloaded.
+       CheckUpdate(oldConfig, newConfig *model.Bootstrap) bool
+
+       // HotReload performs the hot reload of the configuration.
+       HotReload(oldConfig, newConfig *model.Bootstrap) error
+}
+
+// Coordinator listens for configuration file changes and notifies registered 
reloaders
+// to perform hot reload when the configuration changes.
+type Coordinator struct {
+       reloaders []HotReloader         // List of registered reloaders
+       boot      *model.Bootstrap      // Current configuration
+       manager   *config.ConfigManager // Configuration manager
+}
+
+var coordinator = Coordinator{reloaders: []HotReloader{&LoggerReloader{}}}
+
+// StartHotReload initializes the hot reload process.
+// It should be called when the project starts, e.g., in cmd/gateway.go.
+func StartHotReload(manager *config.ConfigManager, boot *model.Bootstrap) {
+       if manager == nil || boot == nil {
+               logger.Warn("ConfigManager or Bootstrap is nil, hot reload will 
not start")
+               return
+       }
+
+       coordinator.manager = manager
+       coordinator.boot = boot
+       go coordinator.HotReload()
+}
+
+// HotReload periodically checks for configuration updates and triggers hot 
reload if changes are detected.
+func (c *Coordinator) HotReload() {
+       for {
+               time.Sleep(constant.CheckConfigInterval)
+
+               boot := c.manager.ViewRemoteConfig()
+               if boot == nil {
+                       continue
+               }
+
+               c.hotReload(boot)
+       }
+}
+
+// hotReload checks for configuration changes and triggers hot reload for 
registered reloaders.
+func (c *Coordinator) hotReload(newBoot *model.Bootstrap) {
+       changed := false
+       wg := &sync.WaitGroup{}
+
+       for _, reloader := range c.reloaders {
+               if reloader.CheckUpdate(c.boot, newBoot) {
+                       changed = true
+                       wg.Add(1)
+                       go func(r HotReloader) {
+                               defer wg.Done()
+                               if err := r.HotReload(c.boot, newBoot); err != 
nil {
+                                       logger.Errorf("Hot reload failed: %v", 
err)
+                               }
+                       }(reloader)
+               }
+       }
+
+       wg.Wait()
+       if changed {
+               c.boot = newBoot
+       }
+}
+
+// equal checks if two string slices are equal.
+func equal(s1, s2 []string) bool {
+       if len(s1) != len(s2) {
+               return false
+       }
+       for i := range s1 {
+               if s1[i] != s2[i] {
+                       return false
+               }
+       }
+       return true
+}
diff --git a/pkg/hotreload/logger.go b/pkg/hotreload/logger.go
new file mode 100644
index 00000000..45430cf3
--- /dev/null
+++ b/pkg/hotreload/logger.go
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package hotreload
+
+import (
+       "github.com/apache/dubbo-go-pixiu/pkg/logger"
+       "github.com/apache/dubbo-go-pixiu/pkg/model"
+)
+
+// LoggerReloader implements the HotReloader interface for reloading logger 
configurations.
+type LoggerReloader struct{}
+
+// CheckUpdate compares the old and new logger configurations to determine if 
a reload is needed.
+func (r *LoggerReloader) CheckUpdate(oldConfig, newConfig *model.Bootstrap) 
bool {
+       oc := oldConfig.Log
+       nc := newConfig.Log
+
+       if oc == nil && nc != nil {
+               return true
+       }
+
+       if oc != nil && nc == nil {
+               return false
+       }
+
+       // Check if any logger configuration fields have changed.
+       if oc.Level != nc.Level ||
+               oc.Development != nc.Development ||
+               oc.DisableCaller != nc.DisableCaller ||
+               oc.DisableStacktrace != nc.DisableStacktrace ||
+               oc.Encoding != nc.Encoding {
+               return false
+       }
+
+       // Check sampling configuration.
+       if !r.checkSampling(oc.Sampling, nc.Sampling) {
+               return false
+       }
+
+       // Check encoder configuration.
+       if !r.checkEncoderConfig(oc.EncoderConfig, nc.EncoderConfig) {
+               return false
+       }
+
+       // Check output paths.
+       if !equal(oc.OutputPaths, nc.OutputPaths) {
+               return false
+       }
+
+       return true
+}
+
+// HotReload applies the new logger configuration.
+func (r *LoggerReloader) HotReload(oldConfig, newConfig *model.Bootstrap) 
error {
+       if err := logger.HotReload(newConfig.Log.Build()); err != nil {
+               logger.Errorf("Failed to reload logger configuration: %v", err)
+               return err
+       }
+       return nil
+}
+
+// checkSampling compares the old and new sampling configurations.
+func (r *LoggerReloader) checkSampling(oldSampling, newSampling 
model.SamplingConfig) bool {
+       return oldSampling.Initial == newSampling.Initial && 
oldSampling.Thereafter == newSampling.Thereafter
+}
+
+// checkEncoderConfig compares the old and new encoder configurations.
+func (r *LoggerReloader) checkEncoderConfig(oldEncoderConfig, newEncoderConfig 
model.EncoderConfig) bool {
+       return oldEncoderConfig.MessageKey == newEncoderConfig.MessageKey &&
+               oldEncoderConfig.LevelKey == newEncoderConfig.LevelKey &&
+               oldEncoderConfig.TimeKey == newEncoderConfig.TimeKey &&
+               oldEncoderConfig.NameKey == newEncoderConfig.NameKey &&
+               oldEncoderConfig.CallerKey == newEncoderConfig.CallerKey &&
+               oldEncoderConfig.FunctionKey == newEncoderConfig.FunctionKey &&
+               oldEncoderConfig.StacktraceKey == 
newEncoderConfig.StacktraceKey &&
+               oldEncoderConfig.SkipLineEnding == 
newEncoderConfig.SkipLineEnding &&
+               oldEncoderConfig.LineEnding == newEncoderConfig.LineEnding &&
+               oldEncoderConfig.EncodeLevel == newEncoderConfig.EncodeLevel &&
+               oldEncoderConfig.EncodeTime == newEncoderConfig.EncodeTime &&
+               oldEncoderConfig.EncodeDuration == 
newEncoderConfig.EncodeDuration &&
+               oldEncoderConfig.EncodeCaller == newEncoderConfig.EncodeCaller 
&&
+               oldEncoderConfig.EncodeName == newEncoderConfig.EncodeName &&
+               oldEncoderConfig.ConsoleSeparator == 
newEncoderConfig.ConsoleSeparator
+}
diff --git a/pkg/logger/controller.go b/pkg/logger/controller.go
index ce015429..91449361 100644
--- a/pkg/logger/controller.go
+++ b/pkg/logger/controller.go
@@ -20,7 +20,9 @@ package logger
 import (
        "strings"
        "sync"
+)
 
+import (
        "go.uber.org/zap"
        "go.uber.org/zap/zapcore"
 )
@@ -42,7 +44,7 @@ func (c *logController) setLoggerLevel(level string) bool {
        }
 
        c.logger.config.Level = *lvl
-       l, _ := c.logger.config.Build()
+       l, _ := c.logger.config.Build(zap.AddCallerSkip(2))
        c.logger = &logger{SugaredLogger: l.Sugar(), config: c.logger.config}
        return true
 }
diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go
index 9cbb49d2..9fd73af1 100644
--- a/pkg/logger/logger.go
+++ b/pkg/logger/logger.go
@@ -107,3 +107,8 @@ func InitLogger(conf *zap.Config) {
 func SetLoggerLevel(level string) bool {
        return control.setLoggerLevel(level)
 }
+
+func HotReload(conf *zap.Config) error {
+       InitLogger(conf)
+       return nil
+}
diff --git a/pkg/model/log.go b/pkg/model/log.go
index 60bb5185..16a8c74f 100644
--- a/pkg/model/log.go
+++ b/pkg/model/log.go
@@ -18,11 +18,14 @@
 package model
 
 import (
-       "github.com/apache/dubbo-go-pixiu/pkg/logger"
        "go.uber.org/zap"
        "go.uber.org/zap/zapcore"
 )
 
+import (
+       "github.com/apache/dubbo-go-pixiu/pkg/logger"
+)
+
 type Log struct {
        Level             string                 `json:"level" yaml:"level"`
        Development       bool                   `json:"development" 
yaml:"development"`


Reply via email to