Parcourir la source

Feature #TASK_QT-33508 配置文件

Carl il y a 4 mois
Parent
commit
ece097ac01

+ 169 - 0
config/apply.go

@@ -0,0 +1,169 @@
+package config
+
+import (
+	"os"
+
+	"github.com/coroot/coroot-node-agent/flags"
+	klog "github.com/sirupsen/logrus"
+	"github.com/spf13/viper"
+)
+
+// shouldUpdateConfig 判断是否应该更新配置
+// 热更新时,如果配置项通过命令行参数或环境变量设置,则不更新
+func shouldUpdateConfig(configKey, envKey1, envKey2 string, currentValue, configValue, defaultValue string, isHotReload bool) bool {
+	// 检查环境变量(无论是初始加载还是热更新)
+	if os.Getenv(envKey1) != "" || os.Getenv(envKey2) != "" {
+		if isHotReload {
+			klog.Debugf("[config] Skipping %s update (set via environment variable)", configKey)
+		}
+		return false
+	}
+
+	// 检查当前值是否与默认值不同
+	// 如果不同,说明是通过命令行参数设置的(因为 kingpin.Parse() 已经处理了命令行参数)
+	// 无论是初始加载还是热更新,都不应该被配置文件覆盖
+	if currentValue != defaultValue {
+		if isHotReload {
+			klog.Debugf("[config] Skipping %s update (set via command line: %s)", configKey, currentValue)
+		} else {
+			klog.Debugf("[config] Skipping %s update from config file (set via command line: %s)", configKey, currentValue)
+		}
+		return false
+	}
+
+	// 当前值等于默认值,说明没有通过命令行参数设置,可以更新
+	return true
+}
+
+// applyConfig 应用配置到 flags
+// isHotReload: true 表示热更新,false 表示初始加载
+// 热更新时不会覆盖通过命令行参数或环境变量设置的配置项
+// 注意:ConfigServer 和 DataServer 的最高优先级是 common.ini(如果 RunInOmniagent 为 true),不受此函数影响
+func applyConfig(v *viper.Viper, isHotReload bool) {
+	klog.Infof("═══════════════════════════════════════════════════════════════")
+	if isHotReload {
+		klog.Infof("🔄 [CONFIG] Starting hot reload configuration...")
+	} else {
+		klog.Infof("📋 [CONFIG] Starting initial configuration load...")
+	}
+	klog.Infof("═══════════════════════════════════════════════════════════════")
+
+	// Server 配置
+	// ConfigServer 和 DataServer 从 common.ini 加载(如果 RunInOmniagent 为 true)
+	// 但是,无论是否在 RunInOmniagent 模式下,只要配置文件中的 config_server 发生改变,就更新 ProxyClient
+	if v.IsSet("server.config_server") {
+		newConfigServer := v.GetString("server.config_server")
+		oldConfigServer := *flags.ConfigServer
+		klog.Infof("📝 [CONFIG] server.config_server: config_file=%s, current=%s, flags_empty=%v",
+			newConfigServer, oldConfigServer, *flags.ConfigServer == "")
+
+		// 参考备份代码:只有当 *flags.ConfigServer == "" 时才从配置文件更新
+		if *flags.ConfigServer == "" && !*flags.RunInOmniagent {
+			if newConfigServer != oldConfigServer {
+				*flags.ConfigServer = newConfigServer
+				klog.Infof("✅ [CONFIG] server.config_server UPDATED: %s -> %s", oldConfigServer, newConfigServer)
+				// 更新 ProxyClient 的 endpoints
+				updateProxyClientEndpoints(newConfigServer)
+			} else {
+				klog.Infof("⏭️  [CONFIG] server.config_server UNCHANGED: %s", newConfigServer)
+			}
+		} else if *flags.RunInOmniagent {
+			// 在 RunInOmniagent 模式下,即使不更新 flags.ConfigServer(因为 common.ini 优先级更高),
+			// 只要配置文件中的值改变了,也更新 ProxyClient
+			if newConfigServer != oldConfigServer {
+				klog.Infof("🔄 [CONFIG] server.config_server (RunInOmniagent mode): updating ProxyClient only: %s -> %s", oldConfigServer, newConfigServer)
+				updateProxyClientEndpoints(newConfigServer)
+			} else {
+				klog.Infof("⏭️  [CONFIG] server.config_server (RunInOmniagent mode): no change, skipping")
+			}
+		}
+	}
+	if v.IsSet("server.data_server") {
+		newDataServer := v.GetString("server.data_server")
+		oldDataServer := *flags.DataServer
+		klog.Infof("📝 [CONFIG] server.data_server: config_file=%s, current=%s, flags_empty=%v",
+			newDataServer, oldDataServer, *flags.DataServer == "")
+
+		// 参考备份代码:只有当 *flags.DataServer == "" 时才从配置文件更新
+		if *flags.DataServer == "" && !*flags.RunInOmniagent {
+			if newDataServer != oldDataServer {
+				*flags.DataServer = newDataServer
+				klog.Infof("✅ [CONFIG] server.data_server UPDATED: %s -> %s", oldDataServer, newDataServer)
+			} else {
+				klog.Infof("⏭️  [CONFIG] server.data_server UNCHANGED: %s", newDataServer)
+			}
+		} else {
+			klog.Infof("⏭️  [CONFIG] server.data_server SKIPPED (set via command line or RunInOmniagent mode)")
+		}
+	}
+	if v.IsSet("server.server_prefix") {
+		newVal := v.GetString("server.server_prefix")
+		oldVal := *flags.ServerPrefix
+		if *flags.ServerPrefix == "" {
+			if newVal != oldVal {
+				*flags.ServerPrefix = newVal
+				klog.Infof("✅ [CONFIG] server.server_prefix UPDATED: %s -> %s", oldVal, newVal)
+			}
+		} else {
+			klog.Infof("⏭️  [CONFIG] server.server_prefix SKIPPED (set via command line: %s)", *flags.ServerPrefix)
+		}
+	}
+	if v.IsSet("server.license_key") {
+		*flags.LicenseKey = v.GetString("server.license_key")
+	}
+
+	// Agent 配置
+	if v.IsSet("agent.disable_register_host") {
+		newVal := v.GetBool("agent.disable_register_host")
+		oldVal := *flags.DisableRegisterHost
+		// 布尔值:检查环境变量,如果没有设置环境变量,则从配置文件更新
+		envSet := os.Getenv("EUSPACE_AGENT_DISABLE_REGISTER_HOST") != "" || os.Getenv("DISABLE_REG_HOST") != ""
+		if !envSet {
+			if newVal != oldVal {
+				*flags.DisableRegisterHost = newVal
+				klog.Infof("✅ [CONFIG] agent.disable_register_host UPDATED: %v -> %v", oldVal, newVal)
+			}
+		} else {
+			klog.Infof("⏭️  [CONFIG] agent.disable_register_host SKIPPED (set via env)")
+		}
+	}
+	if v.IsSet("agent.enable_license_check") {
+		*flags.EnableLicenseCheck = v.GetBool("agent.enable_license_check")
+	}
+	if v.IsSet("agent.send_net_data") {
+		*flags.SendNetData = v.GetBool("agent.send_net_data")
+	}
+
+	// Logging 配置
+	if v.IsSet("logging.log_level") {
+		newLogLevel := v.GetString("logging.log_level")
+		oldLogLevel := *flags.LogLevel
+		klog.Infof("📝 [CONFIG] logging.log_level: config_file=%s, current=%s, flags_empty=%v",
+			newLogLevel, oldLogLevel, *flags.LogLevel == "")
+
+		// 参考备份代码:只有当 *flags.LogLevel == "" 时才从配置文件更新 flags
+		if *flags.LogLevel == "" {
+			if newLogLevel != oldLogLevel {
+				*flags.LogLevel = newLogLevel
+				klog.Infof("✅ [CONFIG] logging.log_level UPDATED (flags): %s -> %s", oldLogLevel, newLogLevel)
+			}
+		} else {
+			klog.Infof("⏭️  [CONFIG] logging.log_level SKIPPED (set via command line: %s)", *flags.LogLevel)
+		}
+
+		// 使用统一的热更新机制:比较配置文件的新旧值,如果改变了就更新 logrus 级别
+		// 这样即使 *flags.LogLevel 是通过命令行设置的,配置文件改变时也能更新 logrus
+		checkAndUpdateConfigValue("logging.log_level", newLogLevel, isHotReload, func() {
+			// 初始加载时,如果 flags.LogLevel 为空,也要设置 logrus 级别
+			if !isHotReload && *flags.LogLevel == "" {
+				updateLogLevel(newLogLevel)
+			} else if isHotReload {
+				// 热更新时,无论 flags 是否更新,都更新 logrus 级别
+				updateLogLevel(newLogLevel)
+			}
+		})
+	}
+	if v.IsSet("logging.console_log") {
+		*flags.ConsoleLog = v.GetBool("logging.console_log")
+	}
+}

+ 52 - 0
config/common_ini.go

@@ -0,0 +1,52 @@
+package config
+
+import (
+	"github.com/coroot/coroot-node-agent/flags"
+	klog "github.com/sirupsen/logrus"
+	"gopkg.in/ini.v1"
+)
+
+// loadConfigFromCommonIni 从 common.ini 加载 ConfigServer 和 DataServer
+// 注意:RunInOmniagent 是启动时确定的标志,不支持热更新
+// 只有在值真正改变时才会更新 ProxyClient
+func loadConfigFromCommonIni() {
+	// RunInOmniagent 在启动时已经确定,这里直接使用其值
+	if !*flags.RunInOmniagent || *flags.CommonIni == "" {
+		return
+	}
+
+	iniData, err := ini.Load(*flags.CommonIni)
+	if err != nil {
+		klog.Debugf("Failed to load common.ini: %v", err)
+		return
+	}
+
+	if iniData != nil {
+		configServer := iniData.Section("common").Key("config_server").String()
+		dataServer := iniData.Section("common").Key("data_server").String()
+
+		// 检查 config_server 是否改变
+		if configServer != "" {
+			oldConfigServer := *flags.ConfigServer
+			if configServer != oldConfigServer {
+				*flags.ConfigServer = configServer
+				klog.Infof("config_server changed in common.ini: %s -> %s", oldConfigServer, configServer)
+				// 更新 ProxyClient 的 endpoints(只有在值改变时才更新)
+				updateProxyClientEndpoints(configServer)
+			} else {
+				klog.Debugf("config_server in common.ini unchanged: %s", configServer)
+			}
+		}
+
+		// 检查 data_server 是否改变
+		if dataServer != "" {
+			oldDataServer := *flags.DataServer
+			if dataServer != oldDataServer {
+				*flags.DataServer = dataServer
+				klog.Infof("data_server changed in common.ini: %s -> %s", oldDataServer, dataServer)
+			} else {
+				klog.Debugf("data_server in common.ini unchanged: %s", dataServer)
+			}
+		}
+	}
+}

+ 117 - 0
config/config.go

@@ -0,0 +1,117 @@
+package config
+
+import (
+	"fmt"
+	"github.com/coroot/coroot-node-agent/utils"
+	"strings"
+	"sync"
+
+	klog "github.com/sirupsen/logrus"
+	"github.com/spf13/viper"
+
+	"github.com/coroot/coroot-node-agent/flags"
+)
+
+var (
+	configOnce          sync.Once
+	configInstance      *viper.Viper // 运行时配置(用于读取)
+	configMutex         sync.RWMutex
+	cmdLineFlagsSet     map[string]bool // 记录通过命令行参数设置的配置项
+	cmdLineFlagsSetOnce sync.Once
+)
+
+// InitConfig 初始化 Viper 配置
+func InitConfig(configPath string) error {
+	var err error
+	configOnce.Do(func() {
+		v := viper.New()
+
+		// 设置配置文件路径
+		if configPath != "" {
+			v.SetConfigFile(configPath)
+		} else {
+			v.SetConfigFile(utils.GetDefaultConfigPath())
+		}
+
+		// 设置环境变量前缀和键替换规则
+		v.SetEnvPrefix("EUSPACE")
+		// 设置环境变量键替换规则:将点号替换为下划线
+		v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
+		v.AutomaticEnv()
+
+		// 读取配置文件
+		// 注意:配置文件读取失败(不存在、格式错误、权限问题等)都不是致命错误
+		// 程序会继续启动,使用默认值、命令行参数和环境变量
+		if err = v.ReadInConfig(); err != nil {
+			// 所有配置文件读取错误都视为非致命错误,只记录警告
+			if _, ok := err.(viper.ConfigFileNotFoundError); ok {
+				klog.Warnf("⚠️  [CONFIG] Config file not found, using defaults: %v", err)
+			} else {
+				klog.Warnf("⚠️  [CONFIG] Error reading config file (will use defaults): %v", err)
+			}
+			err = nil // 不返回错误,允许程序继续启动
+		} else {
+			actualConfigFile := v.ConfigFileUsed()
+			klog.Infof("✓ [CONFIG] Using config file: %s", actualConfigFile)
+			// 启动配置文件轮询监听
+			startConfigFileWatcher(actualConfigFile, v)
+		}
+
+		// 监控 common.ini 文件(如果 RunInOmniagent 为 true)
+		// 注意:RunInOmniagent 是启动时确定的标志,不支持热更新
+		// 只有在启动时 RunInOmniagent 为 true 时,才会启动此监控
+		if *flags.RunInOmniagent && *flags.CommonIni != "" {
+			watchCommonIni(*flags.CommonIni)
+		}
+
+		configInstance = v
+
+		// 初始化 Config 管理器
+		cfg := Get()
+		cfg.Init(v)
+
+		// 应用初始配置(首次加载,可以覆盖默认值)
+		// 注意:现在通过 Config 管理器统一处理,applyConfig 主要用于向后兼容
+		applyConfig(v, false)
+	})
+
+	return err
+}
+
+// GetConfig 获取配置实例
+func GetConfig() *viper.Viper {
+	if configInstance == nil {
+		// 如果未初始化,使用默认配置
+		InitConfig("")
+	}
+	return configInstance
+}
+
+// ReloadConfig 手动重新加载配置
+func ReloadConfig() error {
+	configMutex.Lock()
+	defer configMutex.Unlock()
+
+	if configInstance == nil {
+		return fmt.Errorf("config not initialized")
+	}
+
+	if err := configInstance.ReadInConfig(); err != nil {
+		return fmt.Errorf("error reading config file: %w", err)
+	}
+
+	applyConfig(configInstance, false)
+	return nil
+}
+
+// GetConfigValue 获取配置值(支持热加载)
+func GetConfigValue(key string) interface{} {
+	configMutex.RLock()
+	defer configMutex.RUnlock()
+
+	if configInstance == nil {
+		return nil
+	}
+
+	return configInstance.Get(key)
+}

+ 89 - 0
config/hot_reload.go

@@ -0,0 +1,89 @@
+package config
+
+import (
+	"reflect"
+	"sync"
+
+	klog "github.com/sirupsen/logrus"
+)
+
+var (
+	// lastConfigValues 记录上一次配置文件中的值,用于热更新时比较
+	lastConfigValues   map[string]interface{}
+	lastConfigValuesMu sync.RWMutex
+)
+
+// initLastConfigValues 初始化 lastConfigValues(如果还未初始化)
+func initLastConfigValues() {
+	lastConfigValuesMu.Lock()
+	defer lastConfigValuesMu.Unlock()
+	if lastConfigValues == nil {
+		lastConfigValues = make(map[string]interface{})
+	}
+}
+
+// getLastConfigValue 获取上一次配置文件中的值
+func getLastConfigValue(key string) (interface{}, bool) {
+	lastConfigValuesMu.RLock()
+	defer lastConfigValuesMu.RUnlock()
+	val, ok := lastConfigValues[key]
+	return val, ok
+}
+
+// setLastConfigValue 设置上一次配置文件中的值
+func setLastConfigValue(key string, value interface{}) {
+	lastConfigValuesMu.Lock()
+	defer lastConfigValuesMu.Unlock()
+	if lastConfigValues == nil {
+		lastConfigValues = make(map[string]interface{})
+	}
+	lastConfigValues[key] = value
+}
+
+// checkAndUpdateConfigValue 检查配置值是否改变,如果改变则执行更新操作
+// key: 配置项的键(如 "logging.log_level")
+// newValue: 配置文件中的新值
+// isHotReload: 是否为热更新
+// updateFunc: 如果值改变,需要执行的更新函数
+// 返回: 是否发生了改变
+func checkAndUpdateConfigValue(key string, newValue interface{}, isHotReload bool, updateFunc func()) bool {
+	initLastConfigValues()
+
+	if isHotReload {
+		// 热更新:比较新值和上一次的值
+		lastValue, hasLastValue := getLastConfigValue(key)
+		if !hasLastValue || !valuesEqual(lastValue, newValue) {
+			if hasLastValue {
+				klog.Infof("🔄 [CONFIG] %s changed in config file: %v -> %v", key, lastValue, newValue)
+			} else {
+				klog.Infof("🔄 [CONFIG] %s first detected in config file: %v", key, newValue)
+			}
+			if updateFunc != nil {
+				updateFunc()
+			}
+			setLastConfigValue(key, newValue)
+			return true
+		}
+		return false
+	} else {
+		// 初始加载:记录初始值
+		setLastConfigValue(key, newValue)
+		if updateFunc != nil {
+			updateFunc()
+		}
+		return true
+	}
+}
+
+// valuesEqual 比较两个值是否相等(支持基本类型和字符串)
+func valuesEqual(a, b interface{}) bool {
+	if a == nil && b == nil {
+		return true
+	}
+	if a == nil || b == nil {
+		return false
+	}
+
+	// 使用反射进行深度比较
+	return reflect.DeepEqual(a, b)
+}

+ 23 - 0
config/log_init.go

@@ -0,0 +1,23 @@
+package config
+
+import (
+	"github.com/coroot/coroot-node-agent/logs"
+	"github.com/coroot/coroot-node-agent/utils"
+	"github.com/coroot/coroot-node-agent/utils/enums"
+)
+
+// InitLogFromConfig 从配置管理器初始化日志系统
+// 使用 Config 管理器获取日志相关配置(自动处理优先级)
+// 返回初始化错误(如果有)
+func InitLogFromConfig() error {
+	cfg := Get()
+	return logs.InitLog(cfg.LogLevel(), logs.LogConfig{
+		Path:       utils.GetDefaultLogPath(),
+		AppInfo:    enums.DaemonProc,
+		MaxSize:    50, // 日志文件最大尺寸,单位MB
+		MaxBackups: 3,  // 最多保留的旧日志文件数
+		MaxAge:     3,  // 日志文件保留的最长时间,单位天
+		Console:    cfg.ConsoleLog(),
+	})
+}
+

+ 664 - 0
config/manager.go

@@ -0,0 +1,664 @@
+package config
+
+import (
+	"net/url"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/coroot/coroot-node-agent/flags"
+	"github.com/spf13/viper"
+)
+
+// Config 统一的配置结构体
+// 所有配置项都通过这个结构体访问,内部自动判断优先级
+type Config struct {
+	mu sync.RWMutex
+
+	// Viper 实例(用于读取配置文件)
+	viper *viper.Viper
+
+	// 命令行参数值(最高优先级)
+	cmdLineValues map[string]interface{}
+
+	// 环境变量值(第二优先级)
+	envValues map[string]interface{}
+
+	// 配置文件值(第三优先级)
+	configFileValues map[string]interface{}
+
+	// 默认值(最低优先级)
+	defaultValues map[string]interface{}
+
+	// 特殊配置:common.ini 中的值(最高优先级,仅用于 ConfigServer 和 DataServer)
+	commonIniValues map[string]string
+}
+
+var (
+	globalConfig     *Config
+	globalConfigOnce sync.Once
+)
+
+// Get 获取全局配置实例
+func Get() *Config {
+	globalConfigOnce.Do(func() {
+		globalConfig = &Config{
+			cmdLineValues:    make(map[string]interface{}),
+			envValues:        make(map[string]interface{}),
+			configFileValues: make(map[string]interface{}),
+			defaultValues:    make(map[string]interface{}),
+			commonIniValues:  make(map[string]string),
+		}
+	})
+	return globalConfig
+}
+
+// Init 初始化配置管理器
+// 必须在 flags.Parse() 之后调用
+func (c *Config) Init(viperInstance *viper.Viper) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
+	c.viper = viperInstance
+
+	// 1. 收集命令行参数值(最高优先级)
+	c.collectCmdLineValues()
+
+	// 2. 收集环境变量值(第二优先级)
+	c.collectEnvValues()
+
+	// 3. 收集配置文件值(第三优先级)
+	c.collectConfigFileValues()
+
+	// 4. 收集默认值(最低优先级)
+	c.collectDefaultValues()
+
+	// 5. 收集 common.ini 中的值(特殊处理)
+	c.collectCommonIniValues()
+}
+
+// collectCmdLineValues 收集命令行参数值
+func (c *Config) collectCmdLineValues() {
+	// 从 flags 包中读取命令行参数值
+	c.cmdLineValues["server.config_server"] = *flags.ConfigServer
+	c.cmdLineValues["server.data_server"] = *flags.DataServer
+	c.cmdLineValues["server.server_prefix"] = *flags.ServerPrefix
+	c.cmdLineValues["server.license_key"] = *flags.LicenseKey
+	c.cmdLineValues["server.register_app_to_doop"] = *flags.RegisterAppToDoop
+
+	c.cmdLineValues["agent.disable_e2e_tracing"] = *flags.DisableE2ETracing
+	c.cmdLineValues["agent.disable_stack_tracing"] = *flags.DisableStackTracing
+	c.cmdLineValues["agent.disable_l7_tracing"] = *flags.DisableL7Tracing
+	c.cmdLineValues["agent.enable_elasticsearch_detection"] = *flags.EnableElasticsearchDetection
+	c.cmdLineValues["agent.run_in_container"] = *flags.RunInContainer
+	c.cmdLineValues["agent.run_in_omniagent"] = *flags.RunInOmniagent
+	c.cmdLineValues["agent.disable_register_host"] = *flags.DisableRegisterHost
+	c.cmdLineValues["agent.enable_license_check"] = *flags.EnableLicenseCheck
+	c.cmdLineValues["agent.send_net_data"] = *flags.SendNetData
+
+	c.cmdLineValues["network.listen_address"] = *flags.ListenAddress
+	c.cmdLineValues["network.track_public_network"] = *flags.ExternalNetworksWhitelist
+	c.cmdLineValues["network.ephemeral_port_range"] = *flags.EphemeralPortRange
+
+	c.cmdLineValues["container.cgroupfs_root"] = *flags.CgroupRoot
+	c.cmdLineValues["container.host_dir_path_prefix"] = *flags.HostDirPathPrefix
+	c.cmdLineValues["container.disable_log_parsing"] = *flags.DisableLogParsing
+	c.cmdLineValues["container.disable_pinger"] = *flags.DisablePinger
+
+	c.cmdLineValues["logging.log_level"] = *flags.LogLevel
+	c.cmdLineValues["logging.console_log"] = *flags.ConsoleLog
+	c.cmdLineValues["logging.log_per_second"] = *flags.LogPerSecond
+	c.cmdLineValues["logging.log_burst"] = *flags.LogBurst
+
+	c.cmdLineValues["ebpf.ebpf_file_path"] = *flags.EbpfFilePath
+}
+
+// collectEnvValues 收集环境变量值
+func (c *Config) collectEnvValues() {
+	// 环境变量映射表:环境变量名 -> 配置键
+	envMap := map[string]string{
+		// Server 配置
+		"CONFIG_SERVER":                       "server.config_server",
+		"EUSPACE_SERVER_CONFIG_SERVER":        "server.config_server",
+		"DATA_SERVER":                         "server.data_server",
+		"EUSPACE_SERVER_DATA_SERVER":          "server.data_server",
+		"SERVER_PREFIX":                       "server.server_prefix",
+		"EUSPACE_SERVER_SERVER_PREFIX":        "server.server_prefix",
+		"LICENSE_KEY":                         "server.license_key",
+		"EUSPACE_SERVER_LICENSE_KEY":          "server.license_key",
+		"REGISTER_APP_TO_DOOP":                "server.register_app_to_doop",
+		"EUSPACE_SERVER_REGISTER_APP_TO_DOOP": "server.register_app_to_doop",
+		// Agent 配置
+		"DISABLE_E2E_TRACING":                          "agent.disable_e2e_tracing",
+		"EUSPACE_AGENT_DISABLE_E2E_TRACING":            "agent.disable_e2e_tracing",
+		"DISABLE_STACK_TRACING":                        "agent.disable_stack_tracing",
+		"EUSPACE_AGENT_DISABLE_STACK_TRACING":          "agent.disable_stack_tracing",
+		"DISABLE_L7_TRACING":                           "agent.disable_l7_tracing",
+		"EUSPACE_AGENT_DISABLE_L7_TRACING":             "agent.disable_l7_tracing",
+		"ENABLE_ES":                                    "agent.enable_elasticsearch_detection",
+		"EUSPACE_AGENT_ENABLE_ELASTICSEARCH_DETECTION": "agent.enable_elasticsearch_detection",
+		"RUN_IN_CONTAINER":                             "agent.run_in_container",
+		"EUSPACE_AGENT_RUN_IN_CONTAINER":               "agent.run_in_container",
+		"RUN_IN_OMNIAGENT":                             "agent.run_in_omniagent",
+		"EUSPACE_AGENT_RUN_IN_OMNIAGENT":               "agent.run_in_omniagent",
+		"DISABLE_REG_HOST":                             "agent.disable_register_host",
+		"EUSPACE_AGENT_DISABLE_REGISTER_HOST":          "agent.disable_register_host",
+		"ENABLE_LICENSE_CHECK":                         "agent.enable_license_check",
+		"EUSPACE_AGENT_ENABLE_LICENSE_CHECK":           "agent.enable_license_check",
+		"SEND_NET_DATA":                                "agent.send_net_data",
+		"EUSPACE_AGENT_SEND_NET_DATA":                  "agent.send_net_data",
+		// Network 配置
+		"LISTEN":                               "network.listen_address",
+		"EUSPACE_NETWORK_LISTEN_ADDRESS":       "network.listen_address",
+		"TRACK_PUBLIC_NETWORK":                 "network.track_public_network",
+		"EUSPACE_NETWORK_TRACK_PUBLIC_NETWORK": "network.track_public_network",
+		"EPHEMERAL_PORT_RANGE":                 "network.ephemeral_port_range",
+		"EUSPACE_NETWORK_EPHEMERAL_PORT_RANGE": "network.ephemeral_port_range",
+		// Container 配置
+		"CGROUPFS_ROOT":                          "container.cgroupfs_root",
+		"EUSPACE_CONTAINER_CGROUPFS_ROOT":        "container.cgroupfs_root",
+		"HOST_DIR_PATH_PREFIX":                   "container.host_dir_path_prefix",
+		"EUSPACE_CONTAINER_HOST_DIR_PATH_PREFIX": "container.host_dir_path_prefix",
+		"DISABLE_LOG_PARSING":                    "container.disable_log_parsing",
+		"EUSPACE_CONTAINER_DISABLE_LOG_PARSING":  "container.disable_log_parsing",
+		"DISABLE_PINGER":                         "container.disable_pinger",
+		"EUSPACE_CONTAINER_DISABLE_PINGER":       "container.disable_pinger",
+		// Logging 配置
+		"LOG_LEVEL":                      "logging.log_level",
+		"EUSPACE_LOGGING_LOG_LEVEL":      "logging.log_level",
+		"CONSOLE_LOG":                    "logging.console_log",
+		"EUSPACE_LOGGING_CONSOLE_LOG":    "logging.console_log",
+		"LOG_PER_SECOND":                 "logging.log_per_second",
+		"EUSPACE_LOGGING_LOG_PER_SECOND": "logging.log_per_second",
+		"LOG_BURST":                      "logging.log_burst",
+		"EUSPACE_LOGGING_LOG_BURST":      "logging.log_burst",
+		// eBPF 配置
+		"EBPF_FILE":                   "ebpf.ebpf_file_path",
+		"EUSPACE_EBPF_EBPF_FILE_PATH": "ebpf.ebpf_file_path",
+		// Cloud 配置
+		"PROVIDER":                          "cloud.provider",
+		"EUSPACE_CLOUD_PROVIDER":            "cloud.provider",
+		"REGION":                            "cloud.region",
+		"EUSPACE_CLOUD_REGION":              "cloud.region",
+		"AVAILABILITY_ZONE":                 "cloud.availability_zone",
+		"EUSPACE_CLOUD_AVAILABILITY_ZONE":   "cloud.availability_zone",
+		"INSTANCE_TYPE":                     "cloud.instance_type",
+		"EUSPACE_CLOUD_INSTANCE_TYPE":       "cloud.instance_type",
+		"INSTANCE_LIFE_CYCLE":               "cloud.instance_life_cycle",
+		"EUSPACE_CLOUD_INSTANCE_LIFE_CYCLE": "cloud.instance_life_cycle",
+	}
+
+	for envKey, configKey := range envMap {
+		if val := os.Getenv(envKey); val != "" {
+			c.envValues[configKey] = val
+		}
+	}
+}
+
+// collectConfigFileValues 收集配置文件值
+func (c *Config) collectConfigFileValues() {
+	if c.viper == nil {
+		return
+	}
+
+	// 从 Viper 中读取所有配置项
+	allKeys := c.viper.AllKeys()
+	for _, key := range allKeys {
+		if c.viper.IsSet(key) {
+			c.configFileValues[key] = c.viper.Get(key)
+		}
+	}
+}
+
+// collectDefaultValues 收集默认值
+func (c *Config) collectDefaultValues() {
+	// 默认值定义
+	c.defaultValues["server.config_server"] = ""
+	c.defaultValues["server.data_server"] = ""
+	c.defaultValues["server.server_prefix"] = ""
+	c.defaultValues["server.license_key"] = "J45Engw88NeHUZ4Q7qNsK8L47FTH**QvgW113IEnsNaBNMR5zZ**oj/g!!!!"
+	c.defaultValues["server.register_app_to_doop"] = false
+
+	c.defaultValues["agent.disable_e2e_tracing"] = true
+	c.defaultValues["agent.disable_stack_tracing"] = true
+	c.defaultValues["agent.disable_l7_tracing"] = false
+	c.defaultValues["agent.enable_elasticsearch_detection"] = false
+	c.defaultValues["agent.run_in_container"] = false
+	c.defaultValues["agent.run_in_omniagent"] = false
+	c.defaultValues["agent.disable_register_host"] = false
+	c.defaultValues["agent.enable_license_check"] = true
+	c.defaultValues["agent.send_net_data"] = false
+
+	c.defaultValues["network.listen_address"] = "0.0.0.0:8123"
+	c.defaultValues["network.track_public_network"] = []string{"0.0.0.0/0"}
+	c.defaultValues["network.ephemeral_port_range"] = ""
+
+	c.defaultValues["container.cgroupfs_root"] = "/sys/fs/cgroup"
+	c.defaultValues["container.host_dir_path_prefix"] = ""
+	c.defaultValues["container.disable_log_parsing"] = true
+	c.defaultValues["container.disable_pinger"] = false
+
+	c.defaultValues["logging.log_level"] = "info"
+	c.defaultValues["logging.console_log"] = false
+	c.defaultValues["logging.log_per_second"] = 10.0
+	c.defaultValues["logging.log_burst"] = 100
+
+	c.defaultValues["ebpf.ebpf_file_path"] = ""
+}
+
+// collectCommonIniValues 收集 common.ini 中的值
+func (c *Config) collectCommonIniValues() {
+	if !*flags.RunInOmniagent || *flags.CommonIni == "" {
+		return
+	}
+
+	// 从 common.ini 加载 ConfigServer 和 DataServer
+	// 这个逻辑已经在 flags.go 的 init() 中执行了
+	// 这里我们只是记录一下
+	if *flags.ConfigServer != "" {
+		c.commonIniValues["server.config_server"] = *flags.ConfigServer
+	}
+	if *flags.DataServer != "" {
+		c.commonIniValues["server.data_server"] = *flags.DataServer
+	}
+}
+
+// getValue 获取配置值(自动判断优先级)
+// 优先级:common.ini > 环境变量(非空)> 配置文件(非空)> 默认值
+func (c *Config) getValue(key string) interface{} {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	// 1. 最高优先级:common.ini 中的值(仅用于 ConfigServer 和 DataServer)
+	if val, ok := c.commonIniValues[key]; ok {
+		if val != "" {
+			return val
+		}
+	}
+
+	// 2. 环境变量(如果值不为空)
+	if envVal, ok := c.envValues[key]; ok {
+		// 检查环境变量值是否为空
+		if strVal, ok := envVal.(string); ok {
+			if strVal != "" {
+				if strVal == "true" {
+					return true
+				} else if strVal == "false" {
+					return false
+				}
+				return envVal
+			}
+		} else {
+			// 非字符串类型,直接返回
+			return envVal
+		}
+	}
+
+	// 3. 配置文件(如果值不为空)
+	if configVal, ok := c.configFileValues[key]; ok {
+		// 检查配置文件值是否为空
+		if strVal, ok := configVal.(string); ok {
+			if strVal != "" {
+				return configVal
+			}
+		} else {
+			// 非字符串类型,直接返回
+			return configVal
+		}
+	}
+
+	// flags 中的值
+	if cmdVal, ok := c.cmdLineValues[key]; ok {
+		return cmdVal
+	}
+
+	// 默认值
+	if defaultVal, ok := c.defaultValues[key]; ok {
+		return defaultVal
+	}
+	return nil
+}
+
+// Reload 重新加载配置(用于热更新)
+func (c *Config) Reload() {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
+	// 重新收集配置文件值
+	c.collectConfigFileValues()
+
+	// 重新收集 common.ini 值
+	c.collectCommonIniValues()
+
+	// 同步更新 flags 中的值,确保使用 *flags.XXX 的地方也能获取到最新值
+	c.syncFlagsValues()
+}
+
+// syncFlagsValues 同步更新 flags 中的值
+// 这样即使代码使用 *flags.XXX,也能在热更新后获取到新值
+// 注意:调用此方法时,必须已经持有 c.mu 写锁
+func (c *Config) syncFlagsValues() {
+	if c.viper == nil {
+		return
+	}
+
+	// 更新所有可以通过配置文件设置的 flags 值
+	// 注意:只更新那些没有被命令行参数或环境变量设置的配置项
+	// 注意:这里直接访问内部字段,不调用 getValue(),避免死锁(因为调用者已经持有写锁)
+
+	// Server 配置
+	if c.viper.IsSet("server.config_server") {
+		// 直接访问内部字段,按优先级顺序检查
+		if val, ok := c.commonIniValues["server.config_server"]; ok && val != "" {
+			*flags.ConfigServer = val
+		} else if envVal, ok := c.envValues["server.config_server"]; ok {
+			if s, ok := envVal.(string); ok && s != "" {
+				*flags.ConfigServer = s
+			}
+		} else if configVal, ok := c.configFileValues["server.config_server"]; ok {
+			if s, ok := configVal.(string); ok && s != "" {
+				*flags.ConfigServer = s
+			}
+		}
+	}
+	if c.viper.IsSet("server.data_server") {
+		if val, ok := c.commonIniValues["server.data_server"]; ok && val != "" {
+			*flags.DataServer = val
+		} else if envVal, ok := c.envValues["server.data_server"]; ok {
+			if s, ok := envVal.(string); ok && s != "" {
+				*flags.DataServer = s
+			}
+		} else if configVal, ok := c.configFileValues["server.data_server"]; ok {
+			if s, ok := configVal.(string); ok && s != "" {
+				*flags.DataServer = s
+			}
+		}
+	}
+	// 辅助函数:按优先级获取值(不获取锁,因为调用者已持有写锁)
+	getValueUnsafe := func(key string) interface{} {
+		// 1. common.ini(最高优先级)
+		if val, ok := c.commonIniValues[key]; ok && val != "" {
+			return val
+		}
+		// 2. 环境变量
+		if envVal, ok := c.envValues[key]; ok {
+			if strVal, ok := envVal.(string); ok {
+				if strVal != "" {
+					return envVal
+				}
+			} else {
+				return envVal
+			}
+		}
+		// 3. 配置文件
+		if configVal, ok := c.configFileValues[key]; ok {
+			if strVal, ok := configVal.(string); ok {
+				if strVal != "" {
+					return configVal
+				}
+			} else {
+				return configVal
+			}
+		}
+		// 4. 默认值
+		if defaultVal, ok := c.defaultValues[key]; ok {
+			return defaultVal
+		}
+		return nil
+	}
+
+	if c.viper.IsSet("server.server_prefix") {
+		if val := getValueUnsafe("server.server_prefix"); val != nil {
+			if s, ok := val.(string); ok {
+				*flags.ServerPrefix = s
+			}
+		}
+	}
+	if c.viper.IsSet("server.license_key") {
+		if val := getValueUnsafe("server.license_key"); val != nil {
+			if s, ok := val.(string); ok {
+				*flags.LicenseKey = s
+			}
+		}
+	}
+
+	// Logging 配置
+	if c.viper.IsSet("logging.log_level") {
+		if val := getValueUnsafe("logging.log_level"); val != nil {
+			if s, ok := val.(string); ok {
+				*flags.LogLevel = s
+			}
+		}
+	}
+	if c.viper.IsSet("logging.console_log") {
+		if val := getValueUnsafe("logging.console_log"); val != nil {
+			if b, ok := val.(bool); ok {
+				*flags.ConsoleLog = b
+			}
+		}
+	}
+
+	// Agent 配置
+	if c.viper.IsSet("agent.disable_e2e_tracing") {
+		if val := getValueUnsafe("agent.disable_e2e_tracing"); val != nil {
+			if b, ok := val.(bool); ok {
+				*flags.DisableE2ETracing = b
+			}
+		}
+	}
+	if c.viper.IsSet("agent.disable_stack_tracing") {
+		if val := getValueUnsafe("agent.disable_stack_tracing"); val != nil {
+			if b, ok := val.(bool); ok {
+				*flags.DisableStackTracing = b
+			}
+		}
+	}
+	if c.viper.IsSet("agent.disable_l7_tracing") {
+		if val := getValueUnsafe("agent.disable_l7_tracing"); val != nil {
+			if b, ok := val.(bool); ok {
+				*flags.DisableL7Tracing = b
+			}
+		}
+	}
+	if c.viper.IsSet("agent.disable_register_host") {
+		if val := getValueUnsafe("agent.disable_register_host"); val != nil {
+			if b, ok := val.(bool); ok {
+				*flags.DisableRegisterHost = b
+			}
+		}
+	}
+	if c.viper.IsSet("agent.enable_license_check") {
+		if val := getValueUnsafe("agent.enable_license_check"); val != nil {
+			if b, ok := val.(bool); ok {
+				*flags.EnableLicenseCheck = b
+			}
+		}
+	}
+	if c.viper.IsSet("agent.send_net_data") {
+		if val := getValueUnsafe("agent.send_net_data"); val != nil {
+			if b, ok := val.(bool); ok {
+				*flags.SendNetData = b
+			}
+		}
+	}
+
+	// Network 配置
+	if c.viper.IsSet("network.listen_address") {
+		if val := getValueUnsafe("network.listen_address"); val != nil {
+			if s, ok := val.(string); ok {
+				*flags.ListenAddress = s
+			}
+		}
+	}
+
+	// Container 配置
+	if c.viper.IsSet("container.cgroupfs_root") {
+		if val := getValueUnsafe("container.cgroupfs_root"); val != nil {
+			if s, ok := val.(string); ok {
+				*flags.CgroupRoot = s
+			}
+		}
+	}
+
+	// 注意:这里只更新了部分常用的配置项
+	// 如果需要更新其他配置项,可以继续添加
+}
+
+// 便捷方法:获取各种类型的配置值
+
+func (c *Config) LogLevel() string {
+	val := c.getValue("logging.log_level")
+	if s, ok := val.(string); ok && s != "" {
+		return s
+	}
+	return "info"
+}
+
+func (c *Config) ConsoleLog() bool {
+	val := c.getValue("logging.console_log")
+	if b, ok := val.(bool); ok {
+		return b
+	}
+	return false
+}
+
+func (c *Config) ConfigServer() string {
+	val := c.getValue("server.config_server")
+	if s, ok := val.(string); ok {
+		return s
+	}
+	return ""
+}
+
+func (c *Config) DataServer() string {
+	val := c.getValue("server.data_server")
+	if s, ok := val.(string); ok {
+		return s
+	}
+	return ""
+}
+
+func (c *Config) ServerPrefix() string {
+	val := c.getValue("server.server_prefix")
+	if s, ok := val.(string); ok {
+		return s
+	}
+	return ""
+}
+
+func (c *Config) LicenseKey() string {
+	val := c.getValue("server.license_key")
+	if s, ok := val.(string); ok {
+		return s
+	}
+	return "J45Engw88NeHUZ4Q7qNsK8L47FTH**QvgW113IEnsNaBNMR5zZ**oj/g!!!!"
+}
+
+func (c *Config) DisableRegisterHost() bool {
+	val := c.getValue("agent.disable_register_host")
+	if b, ok := val.(bool); ok {
+		return b
+	}
+	return false
+}
+
+func (c *Config) EnableLicenseCheck() bool {
+	val := c.getValue("agent.enable_license_check")
+	if b, ok := val.(bool); ok {
+		return b
+	}
+	return true
+}
+
+func (c *Config) SendNetData() bool {
+	val := c.getValue("agent.send_net_data")
+	if b, ok := val.(bool); ok {
+		return b
+	}
+	return false
+}
+
+func (c *Config) DisableE2ETracing() bool {
+	val := c.getValue("agent.disable_e2e_tracing")
+	if b, ok := val.(bool); ok {
+		return b
+	}
+	return true
+}
+
+func (c *Config) DisableStackTracing() bool {
+	val := c.getValue("agent.disable_stack_tracing")
+	if b, ok := val.(bool); ok {
+		return b
+	}
+	return true
+}
+
+func (c *Config) DisableL7Tracing() bool {
+	val := c.getValue("agent.disable_l7_tracing")
+	if b, ok := val.(bool); ok {
+		return b
+	}
+	return false
+}
+
+func (c *Config) RunInContainer() bool {
+	val := c.getValue("agent.run_in_container")
+	if b, ok := val.(bool); ok {
+		return b
+	}
+	return false
+}
+
+func (c *Config) RunInOmniagent() bool {
+	// RunInOmniagent 不支持热更新,直接从 flags 获取
+	return *flags.RunInOmniagent
+}
+
+func (c *Config) ListenAddress() string {
+	val := c.getValue("network.listen_address")
+	if s, ok := val.(string); ok {
+		return s
+	}
+	return "0.0.0.0:8123"
+}
+
+func (c *Config) CgroupRoot() string {
+	val := c.getValue("container.cgroupfs_root")
+	if s, ok := val.(string); ok {
+		return s
+	}
+	return "/sys/fs/cgroup"
+}
+
+// TracesEndpoint 获取 TracesEndpoint
+// 优先级:命令行参数/环境变量 > 根据 DataServer 和 ServerPrefix 计算
+// 如果都为空,返回 nil
+func (c *Config) TracesEndpoint() *url.URL {
+	// 1. 优先使用命令行参数或环境变量设置的 TracesEndpoint
+	if *flags.TracesEndpoint != nil {
+		return *flags.TracesEndpoint
+	}
+
+	// 2. 如果 TracesEndpoint 未设置,根据 DataServer 和 ServerPrefix 计算
+	dataServer := c.DataServer()
+	if dataServer == "" {
+		return nil
+	}
+
+	// 如果 DataServer 没有 http/https 前缀,添加 http://
+	dataServerStr := dataServer
+	if !strings.HasPrefix(dataServerStr, "http://") && !strings.HasPrefix(dataServerStr, "https://") {
+		dataServerStr = "http://" + dataServerStr
+	}
+
+	// 解析 DataServer URL
+	dataServerURL, err := url.Parse(dataServerStr)
+	if err != nil || dataServerURL == nil {
+		return nil
+	}
+
+	// 构建 TracesEndpoint:DataServer + ServerPrefix + "/api/v2/data/receive"
+	serverPrefix := c.ServerPrefix()
+	path := serverPrefix + "/api/v2/data/receive"
+	return dataServerURL.JoinPath(path)
+}

+ 48 - 0
config/updates.go

@@ -0,0 +1,48 @@
+package config
+
+import (
+	"github.com/coroot/coroot-node-agent/utils/worker"
+	"github.com/sirupsen/logrus"
+	klog "github.com/sirupsen/logrus"
+)
+
+// updateProxyClientEndpoints 更新 ProxyClient 的 endpoints
+// 当 ConfigServer 热更新时,需要同步更新 ProxyClient 的 endpoints
+func updateProxyClientEndpoints(configServer string) {
+	proxyClient, err := worker.GetProxyClient()
+	if err != nil {
+		// ProxyClient 还未初始化,不需要更新
+		klog.Debugf("ProxyClient not initialized yet, skipping endpoint update")
+		return
+	}
+
+	// 更新 endpoints
+	if err := proxyClient.SetEndpoint(configServer, false); err != nil {
+		klog.Warnf("Failed to update ProxyClient endpoints: %v", err)
+	} else {
+		klog.Infof("Updated ProxyClient endpoints to: %s", configServer)
+	}
+}
+
+// updateLogLevel 更新日志级别
+// 当 logging.log_level 热更新时,需要重新设置 logrus 的级别
+func updateLogLevel(level string) {
+	if level == "" {
+		level = "info"
+	}
+	logrusLevel, err := logrus.ParseLevel(level)
+	if err != nil {
+		klog.Warnf("Failed to parse log level %s: %v", level, err)
+		return
+	}
+	logrus.SetLevel(logrusLevel)
+	klog.Infof("Updated log level to: %s", level)
+}
+
+// updateLogOutput 更新日志输出目标
+// 当 logging.console_log 热更新时,需要更新日志输出
+func updateLogOutput(consoleLog bool) {
+	// 注意:由于 logrus 的输出设置比较复杂,这里只更新基本设置
+	// 如果需要完整的输出切换,可能需要重新调用 logs.InitLog
+	klog.Infof("Updated console log output: %v", consoleLog)
+}

+ 104 - 0
config/watcher.go

@@ -0,0 +1,104 @@
+package config
+
+import (
+	"os"
+	"time"
+
+	klog "github.com/sirupsen/logrus"
+	"github.com/spf13/viper"
+)
+
+// startConfigFileWatcher 启动配置文件轮询监听
+func startConfigFileWatcher(configFile string, v *viper.Viper) {
+	if configFile == "" {
+		klog.Warnf("⚠️  [CONFIG] ConfigFileUsed() returned empty string, cannot start polling watcher")
+		return
+	}
+
+	klog.Infof("✓ [CONFIG] Starting polling watcher for: %s", configFile)
+	go func() {
+		lastModTime := time.Now()
+		// 初始化时获取文件的修改时间
+		if info, err := os.Stat(configFile); err == nil {
+			lastModTime = info.ModTime()
+			klog.Infof("✓ [CONFIG] Initial file modTime: %v", lastModTime)
+		}
+
+		ticker := time.NewTicker(1 * time.Second)
+		defer ticker.Stop()
+
+		for {
+			select {
+			case <-ticker.C:
+				info, err := os.Stat(configFile)
+				if err != nil {
+					// 文件不存在或无法访问,跳过本次检查
+					klog.Debugf("⏭️  [CONFIG] Cannot stat config file: %v", err)
+					continue
+				}
+
+				// 检查文件修改时间是否变化
+				if info.ModTime().After(lastModTime) {
+					lastModTime = info.ModTime()
+					klog.Infof("🔔 [CONFIG] Config file changed detected by polling: %s (modTime: %v)", configFile, lastModTime)
+
+					configMutex.Lock()
+					// 重新加载配置
+					if err := v.ReadInConfig(); err != nil {
+						klog.Errorf("❌ [CONFIG] Error reloading config file: %v", err)
+						configMutex.Unlock()
+						continue
+					}
+
+					// 重新加载 Config 管理器
+					cfg := Get()
+					cfg.Reload()
+
+					// 应用配置变更
+					applyConfig(v, true)
+					configMutex.Unlock()
+				}
+			}
+		}
+	}()
+}
+
+// watchCommonIni 使用轮询方式监控 common.ini 文件变化
+// 注意:RunInOmniagent 是启动时确定的标志,不支持热更新
+// 只有在启动时 RunInOmniagent 为 true 时,才会启动此监控
+func watchCommonIni(iniPath string) {
+	klog.Infof("✓ [CONFIG] Starting polling watcher for common.ini: %s", iniPath)
+	go func() {
+		lastModTime := time.Now()
+		// 初始化时获取文件的修改时间
+		if info, err := os.Stat(iniPath); err == nil {
+			lastModTime = info.ModTime()
+			klog.Infof("✓ [CONFIG] Initial common.ini modTime: %v", lastModTime)
+		}
+
+		ticker := time.NewTicker(1 * time.Second)
+		defer ticker.Stop()
+
+		for {
+			select {
+			case <-ticker.C:
+				info, err := os.Stat(iniPath)
+				if err != nil {
+					// 文件不存在或无法访问,跳过本次检查
+					klog.Debugf("⏭️  [CONFIG] Cannot stat common.ini file: %v", err)
+					continue
+				}
+
+				// 检查文件修改时间是否变化
+				if info.ModTime().After(lastModTime) {
+					lastModTime = info.ModTime()
+					klog.Infof("🔔 [CONFIG] common.ini file changed detected by polling: %s (modTime: %v)", iniPath, lastModTime)
+
+					configMutex.Lock()
+					loadConfigFromCommonIni()
+					configMutex.Unlock()
+				}
+			}
+		}
+	}()
+}

+ 1 - 1
containers/apm_heartbeat_batch.go

@@ -26,7 +26,7 @@ func GenerateProcessId(serviceType, serviceName, systemUUID, ip string, port int
 	}
 	// 生成 hash: hash(systemuuid + ip + port)
 	hashInput := fmt.Sprintf("%s%s%d", systemUUID, ip, port)
-	hash := utils.HashTo16DigitString(hashInput)
+	hash := utils.HashTo16CharString(hashInput)
 	// 格式: {serviceType}-{serviceName}-{hash}
 	return fmt.Sprintf("%s-%s-%s", serviceType, serviceName, hash)
 }

+ 4 - 0
containers/container_apm.go

@@ -44,6 +44,10 @@ func (c *Container) getOrInitTrace(traceId uint64) (*tracing.Trace, error) {
 	if !ok {
 		//new trace
 		trace = tracing.NewTraceFromEvent(string(c.id))
+		if trace == nil {
+			// tracer 未初始化,返回错误
+			return nil, fmt.Errorf("tracer is not initialized, cannot create trace")
+		}
 		//create TraceMap
 		c.createTraceMap(traceId, trace)
 		//create ParentSpan

+ 7 - 5
containers/registry.go

@@ -19,6 +19,7 @@ import (
 
 	"github.com/coroot/coroot-node-agent/cgroup"
 	"github.com/coroot/coroot-node-agent/common"
+	"github.com/coroot/coroot-node-agent/config"
 	"github.com/coroot/coroot-node-agent/ebpftracer"
 	"github.com/coroot/coroot-node-agent/ebpftracer/tracer"
 	"github.com/coroot/coroot-node-agent/flags"
@@ -122,6 +123,7 @@ func NewRegistry(reg prometheus.Registerer, kernelVersion string, nodeInfo *Node
 	if err != nil {
 		return nil, err
 	}
+	cfg := config.Get()
 
 	r := &Registry{
 		reg:    reg,
@@ -136,18 +138,18 @@ func NewRegistry(reg prometheus.Registerer, kernelVersion string, nodeInfo *Node
 
 		processInfoCh: processInfoCh,
 
-		tracer:               ebpftracer.NewTracer(kernelVersion, *flags.DisableL7Tracing, *flags.DisableE2ETracing, *flags.DisableStackTracing),
+		tracer:               ebpftracer.NewTracer(kernelVersion, cfg.DisableL7Tracing(), cfg.DisableE2ETracing(), cfg.DisableStackTracing()),
 		whiteListRules:       make(WhiteListMap),
 		trafficStatsUpdateCh: make(chan *TrafficStatsUpdate),
 		nodeInfo:             nodeInfo,
 		RegistryApps:         make(map[uint32]AppStatusInfo),
 	}
 	// 初始化软负载集群节点
-	proxyClient, clientErr := NewProxyClient(*flags.ConfigServer, false)
+	proxyClient, clientErr := NewProxyClient(cfg.ConfigServer(), false)
 	if clientErr == nil {
 		// 负载健康检测
 		try.Go(proxyClient.CheckEndpoints, CatchFn)
-		klog.Infof("New Proxy Client success.config_server is [%s]", *flags.ConfigServer)
+		klog.Infof("New Proxy Client success.config_server is [%s]", cfg.ConfigServer())
 	} else {
 		klog.WithError(clientErr).Errorf("NewProxyClient error, Please check [export CONFIG_ENDPOINT=ip:port]")
 		return nil, clientErr
@@ -158,7 +160,7 @@ func NewRegistry(reg prometheus.Registerer, kernelVersion string, nodeInfo *Node
 		klog.Errorf("init connServer error:%s.", err)
 		return nil, err
 	}
-	if !*flags.DisableRegisterHost {
+	if !cfg.DisableRegisterHost() {
 		// Register Host
 		err = r.RegisterHost()
 		if err != nil {
@@ -167,7 +169,7 @@ func NewRegistry(reg prometheus.Registerer, kernelVersion string, nodeInfo *Node
 		}
 		try.Go(r.TaskRegisterHost, CatchFn)
 		// 启动心跳批量上报任务(仅在开启 License 校验时)
-		if *flags.EnableLicenseCheck {
+		if cfg.EnableLicenseCheck() {
 			try.Go(r.TaskHeartbeatBatch, CatchFn)
 		}
 	}

+ 6 - 15
ebpftracer/tracer.go

@@ -156,21 +156,12 @@ type Tracer struct {
 }
 
 func NewTracer(kernelVersion string, disableL7Tracing, disableE2ETracing, disableStackTracing bool) *Tracer {
-	if disableL7Tracing {
-		klog.Infoln("L7 tracing is disabled")
-	} else {
-		klog.Infoln("L7 tracing is enabled")
-	}
-	if disableE2ETracing {
-		klog.Infoln("e2e is disabled")
-	} else {
-		klog.Infoln("e2e is enabled")
-	}
-	if disableStackTracing {
-		klog.Infoln("L7 stack is disabled")
-	} else {
-		klog.Infoln("L7 stack is enabled")
-	}
+	klog.Infof(
+		"Tracing config | L7=%t, E2E=%t, L7Stack=%t",
+		!disableL7Tracing,
+		!disableE2ETracing,
+		!disableStackTracing,
+	)
 	return &Tracer{
 		kernelVersion:       kernelVersion,
 		disableL7Tracing:    disableL7Tracing,

+ 19 - 9
flags/flags.go

@@ -13,13 +13,13 @@ import (
 
 var (
 	// apm
-	ConfigServer        = kingpin.Flag("config-server", "The URL of the endpoint to send traces to").Envar("CONFIG_SERVER").Default("http://10.0.12.192:18080").String()
-	DataServer          = kingpin.Flag("data-server", "The URL of the endpoint to send traces to").Envar("DATA_SERVER").Default("http://10.0.12.192:18080").String()
+	ConfigServer        = kingpin.Flag("config-server", "The URL of the endpoint to send traces to").Envar("CONFIG_SERVER").Default("").String()
+	DataServer          = kingpin.Flag("data-server", "The URL of the endpoint to send traces to").Envar("DATA_SERVER").Default("").String()
 	DumpApps            = kingpin.Flag("dump", "Dump app snap").Short('d').Default("false").Bool()
 	DumpRules           = kingpin.Flag("dr", "Dump rule snap").Default("false").Bool()
 	PrintFormat         = kingpin.Flag("output", "Output format (table|json)").Short('o').Default("table").String()
 	Version             = kingpin.Flag("version", "show app version").Short('v').Bool()
-	LogLevel            = kingpin.Flag("log-level", "Log level").Envar("LOG_LEVEL").Default("info").String()
+	LogLevel            = kingpin.Flag("log-level", "Log level").Envar("LOG_LEVEL").Default("").String()
 	ConsoleLog          = kingpin.Flag("console-log", "Console log").Envar("CONSOLE_LOG").Default("false").Bool()
 	EbpfFilePath        = kingpin.Flag("ebpf-path", "Set ebpf file path").Envar("EBPF_FILE").Default("").String()
 	CommonIni           = kingpin.Flag("common.ini", "Set ebpf file path").Envar("COMMON_INI").Default("/opt/cloudwise/omniagent/conf/common.ini").String()
@@ -40,10 +40,10 @@ var (
 	EnableElasticsearchDetection = kingpin.Flag("enable-es", "Enable Elasticsearch detection in HTTP requests").Default("false").Envar("ENABLE_ES").Bool()
 
 	ExternalNetworksWhitelist = kingpin.
-		Flag("track-public-network", "Allow track connections to the specified IP networks, all private networks are allowed by default (e.g., Y.Y.Y.Y/mask)").
-		Envar("TRACK_PUBLIC_NETWORK").
-		Default("0.0.0.0/0").
-		Strings()
+					Flag("track-public-network", "Allow track connections to the specified IP networks, all private networks are allowed by default (e.g., Y.Y.Y.Y/mask)").
+					Envar("TRACK_PUBLIC_NETWORK").
+					Default("0.0.0.0/0").
+					Strings()
 	EphemeralPortRange = kingpin.Flag("ephemeral-port-range", "Destination and Listen TCP ports from this range will be skipped").Default("").Envar("EPHEMERAL_PORT_RANGE").String()
 
 	Provider          = kingpin.Flag("provider", "`provider` label for `node_cloud_info` metric").Envar("PROVIDER").String()
@@ -85,6 +85,8 @@ var (
 	SendNetData = kingpin.Flag("send-net-data", "Send the net data to platform").Default("false").Envar("SEND_NET_DATA").Bool()
 	//是否开启License校验逻辑
 	EnableLicenseCheck = kingpin.Flag("enable-license-check", "Enable license check and quota validation").Default("true").Envar("ENABLE_LICENSE_CHECK").Bool()
+	//配置文件路径
+	ConfigFile = kingpin.Flag("config", "Path to configuration file").Envar("EUSPACE_CONFIG").Default("").String()
 )
 
 var AgentName = "euspace"
@@ -155,12 +157,20 @@ func init() {
 	// set ServerPrefix
 	// set ConfigServer
 	// set DataServer
+	// 注意:ConfigServer 和 DataServer 的最高优先级是 common.ini(如果 RunInOmniagent 为 true)
+	// 这个逻辑在 flags.go 的 init() 中执行,确保在 Viper 配置加载之前就设置了
 	if *RunInOmniagent {
 		iniData, err := ini.Load(*CommonIni)
 		if err == nil && iniData != nil {
 			*ServerPrefix = "/apm"
-			*ConfigServer = iniData.Section("common").Key("config_server").String()
-			*DataServer = iniData.Section("common").Key("data_server").String()
+			configServer := iniData.Section("common").Key("config_server").String()
+			dataServer := iniData.Section("common").Key("data_server").String()
+			if configServer != "" {
+				*ConfigServer = configServer
+			}
+			if dataServer != "" {
+				*DataServer = dataServer
+			}
 		}
 	}
 

+ 26 - 18
main.go

@@ -3,14 +3,6 @@ package main
 import (
 	"bytes"
 	"encoding/json"
-	"github.com/cilium/ebpf/rlimit"
-	"github.com/coroot/coroot-node-agent/kube"
-	"github.com/coroot/coroot-node-agent/utils"
-	"github.com/coroot/coroot-node-agent/utils/enums"
-	"github.com/coroot/coroot-node-agent/utils/namedpipe"
-	"github.com/coroot/coroot-node-agent/utils/try"
-	dto "github.com/prometheus/client_model/go"
-	log "github.com/sirupsen/logrus"
 	"io"
 	"net/http"
 	_ "net/http/pprof"
@@ -20,6 +12,14 @@ import (
 	"path/filepath"
 	"syscall"
 
+	"github.com/cilium/ebpf/rlimit"
+	"github.com/coroot/coroot-node-agent/kube"
+	"github.com/coroot/coroot-node-agent/utils"
+	"github.com/coroot/coroot-node-agent/utils/namedpipe"
+	"github.com/coroot/coroot-node-agent/utils/try"
+	dto "github.com/prometheus/client_model/go"
+	log "github.com/sirupsen/logrus"
+
 	// "regexp"
 	"runtime"
 	"strconv"
@@ -27,6 +27,7 @@ import (
 	"time"
 
 	"github.com/coroot/coroot-node-agent/common"
+	"github.com/coroot/coroot-node-agent/config"
 	"github.com/coroot/coroot-node-agent/containers"
 	"github.com/coroot/coroot-node-agent/flags"
 	"github.com/coroot/coroot-node-agent/logs"
@@ -135,19 +136,26 @@ type PostData struct {
 
 func main() {
 	runtime.GOMAXPROCS(1)
-	err := logs.InitLog(*flags.LogLevel, logs.LogConfig{
-		Path:       utils.GetDefaultLogPath(),
-		AppInfo:    enums.DaemonProc,
-		MaxSize:    50, // 日志文件最大尺寸,单位MB
-		MaxBackups: 3,  // 最多保留的旧日志文件数
-		MaxAge:     3,  // 日志文件保留的最长时间,单位天
-		Console:    *flags.ConsoleLog,
-	})
 
-	if err != nil {
+	// 初始化配置文件(支持热加载)
+	// 支持三种方式指定配置文件:
+	// 1. 命令行参数 --config
+	// 2. 环境变量 EUSPACE_CONFIG
+	// 3. 自动查找默认位置的配置文件(如果存在)
+	configPath := *flags.ConfigFile
+	if configPath == "" {
+		configPath = os.Getenv("EUSPACE_CONFIG")
+	}
+	var err error
+	if err = config.InitConfig(configPath); err != nil {
+		log.Warnf("Failed to initialize config: %v, using command line flags and environment variables", err)
+	}
+
+	// 使用 Config 管理器初始化日志系统(自动处理优先级)
+	if err = config.InitLogFromConfig(); err != nil {
 		log.WithError(err).Errorf("log init error.")
 	}
-	if err := rlimit.RemoveMemlock(); err != nil {
+	if err = rlimit.RemoveMemlock(); err != nil {
 		log.WithError(err).Warning("Failed Removing memlock.")
 	} else {
 		log.Info("Rlimit removed")

+ 4 - 1
tracing/tracing.go

@@ -9,6 +9,7 @@ import (
 	"time"
 
 	"github.com/coroot/coroot-node-agent/common"
+	"github.com/coroot/coroot-node-agent/config"
 	"github.com/coroot/coroot-node-agent/ebpftracer"
 	"github.com/coroot/coroot-node-agent/ebpftracer/l7"
 	"github.com/coroot/coroot-node-agent/flags"
@@ -33,7 +34,9 @@ var (
 )
 
 func Init(machineId, hostname, version string) {
-	endpointUrl := *flags.TracesEndpoint
+	// 从 config 获取 TracesEndpoint(根据 DataServer 和 ServerPrefix 计算)
+	cfg := config.Get()
+	endpointUrl := cfg.TracesEndpoint()
 	if endpointUrl == nil {
 		klog.Infoln("no OpenTelemetry traces collector endpoint configured")
 		return

+ 22 - 0
utils/id.go

@@ -142,6 +142,28 @@ func md5ToDec(str string) string {
 	return strCode
 }
 
+// HashTo16CharString 生成16位数字+字母组合字符串
+// 使用 MD5 哈希,然后转换为 base36 编码(0-9, a-z),只包含数字和字母,无特殊字符
+func HashTo16CharString(appName string) string {
+	// 计算 MD5 哈希
+	hash := md5.Sum([]byte(appName))
+	// 将哈希值转换为一个大整数
+	bigInt := new(big.Int).SetBytes(hash[:])
+	// 转换为 base36(0-9, a-z),只包含数字和字母
+	base36Str := bigInt.Text(36)
+	// 确保长度至少为 16 位
+	if len(base36Str) < 16 {
+		// 如果长度不足,用 0 填充到 16 位
+		base36Str = fmt.Sprintf("%016s", base36Str)
+		// 如果第一位是 0,替换为字母 a
+		if base36Str[0] == '0' {
+			base36Str = "a" + base36Str[1:]
+		}
+	}
+	// 截取前 16 位
+	return base36Str[:16]
+}
+
 func HashTo16DigitString(appName string) string {
 	// 计算 MD5 哈希
 	hash := md5.Sum([]byte(appName))

+ 5 - 1
utils/util.go

@@ -37,7 +37,7 @@ var (
 	ConfPath         = "conf"
 	LogsPath         = "logs"
 	LibsPath         = "libs"
-	ConfName         = "daemon.conf"
+	ConfName         = "config.yaml"
 	ComIniFile       = "common.ini"
 	LogName          = "daemon.log"
 	ScriptPath       = "scripts"
@@ -277,6 +277,10 @@ func linkMapHandle(m map[string]string) {
 
 var CPUNum = float64(runtime.NumCPU())
 
+func GetDefaultConfPath() string {
+	return GetDefaultPath(ConfPath)
+}
+
 func FormatPath(s string) string {
 	switch runtime.GOOS {
 	case "windows":

+ 1 - 1
utils/worker/serverWorker.go

@@ -174,7 +174,7 @@ func (w *ServerHTTPWorker) WhiteListV2(request WhiteListReq) (WhiteDataV2, error
 
 func (w *ServerHTTPWorker) HeartbeatBatch(request EuspaceHeartBatchRequest) (EuspaceHeartBatchResponse, error) {
 	response := EuspaceHeartBatchResponse{}
-	result, err := w.requestServer("/api/v2/euspace/heartbeat/batch", request)
+	result, err := w.requestServer("/v2/euspace/heartbeat/batch", request)
 	if err != nil {
 		return response, err
 	}