diff --git a/src/common/const.go b/src/common/const.go index e6a29ceb0..b4bd31f86 100755 --- a/src/common/const.go +++ b/src/common/const.go @@ -169,4 +169,21 @@ const ( MetricEnable = "metric_enable" MetricPort = "metric_port" MetricPath = "metric_path" + + // Trace setting items + TraceEnabled = "trace_enabled" + TraceServiceName = "trace_service_name" + TraceSampleRate = "trace_sample_rate" + TraceNamespace = "trace_namespace" + TraceAttributes = "trace_attribute" + TraceJaegerEndpoint = "trace_jaeger_endpoint" + TraceJaegerUsername = "trace_jaeger_username" + TraceJaegerPassword = "trace_jaeger_password" + TraceJaegerAgentHost = "trace_jaeger_agent_host" + TraceJaegerAgentPort = "trace_jaeger_agent_port" + TraceOtelEndpoint = "trace_otel_endpoint" + TraceOtelURLPath = "trace_otel_url_path" + TraceOtelCompression = "trace_otel_compression" + TraceOtelInsecure = "trace_otel_insecure" + TraceOtelTimeout = "trace_otel_timeout" ) diff --git a/src/common/http/transport.go b/src/common/http/transport.go index eab04b354..381aff4e6 100644 --- a/src/common/http/transport.go +++ b/src/common/http/transport.go @@ -20,7 +20,6 @@ import ( "net/http" "time" - tracelib "github.com/goharbor/harbor/src/lib/trace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) @@ -43,10 +42,11 @@ func init() { } else { secureHTTPTransport = NewTransport() } - if tracelib.Enabled() { - insecureHTTPTransport = otelhttp.NewTransport(insecureHTTPTransport) - secureHTTPTransport = otelhttp.NewTransport(secureHTTPTransport) - } +} + +func AddTracingWithGlobalTransport() { + insecureHTTPTransport = otelhttp.NewTransport(insecureHTTPTransport) + secureHTTPTransport = otelhttp.NewTransport(secureHTTPTransport) } // Use this instead of Default Transport in library because it sets ForceAttemptHTTP2 to true diff --git a/src/core/main.go b/src/core/main.go index b027d1f97..6c8d9e30a 100755 --- a/src/core/main.go +++ b/src/core/main.go @@ -54,6 +54,7 @@ import ( "github.com/goharbor/harbor/src/lib/orm" tracelib "github.com/goharbor/harbor/src/lib/trace" "github.com/goharbor/harbor/src/migration" + _ "github.com/goharbor/harbor/src/pkg/config/inmemory" "github.com/goharbor/harbor/src/pkg/notification" _ "github.com/goharbor/harbor/src/pkg/notifier/topic" "github.com/goharbor/harbor/src/pkg/oidc" @@ -187,6 +188,7 @@ func main() { go metric.ServeProm(metricCfg.Path, metricCfg.Port) } ctx := context.Background() + config.InitTraceConfig(ctx) shutdownTracerProvider := tracelib.InitGlobalTracer(ctx) token.InitCreators() database, err := config.Database() diff --git a/src/jobservice/main.go b/src/jobservice/main.go index d707abce0..1bbfd8ef0 100644 --- a/src/jobservice/main.go +++ b/src/jobservice/main.go @@ -29,6 +29,7 @@ import ( "github.com/goharbor/harbor/src/jobservice/runtime" cfgLib "github.com/goharbor/harbor/src/lib/config" tracelib "github.com/goharbor/harbor/src/lib/trace" + _ "github.com/goharbor/harbor/src/pkg/config/inmemory" _ "github.com/goharbor/harbor/src/pkg/config/rest" ) @@ -59,6 +60,7 @@ func main() { panic(err) } + cfgLib.InitTraceConfig(ctx) defer tracelib.InitGlobalTracer(context.Background()).Shutdown() // Set job context initializer diff --git a/src/lib/config/metadata/metadatalist.go b/src/lib/config/metadata/metadatalist.go index 54062e839..e115023aa 100644 --- a/src/lib/config/metadata/metadatalist.go +++ b/src/lib/config/metadata/metadatalist.go @@ -156,11 +156,28 @@ var ( {Name: common.RobotTokenDuration, Scope: UserScope, Group: BasicGroup, EnvKey: "ROBOT_TOKEN_DURATION", DefaultValue: "30", ItemType: &IntType{}, Editable: true, Description: `The robot account token duration in days`}, {Name: common.RobotNamePrefix, Scope: UserScope, Group: BasicGroup, EnvKey: "ROBOT_NAME_PREFIX", DefaultValue: "robot$", ItemType: &StringType{}, Editable: true, Description: `The rebot account name prefix`}, {Name: common.NotificationEnable, Scope: UserScope, Group: BasicGroup, EnvKey: "NOTIFICATION_ENABLE", DefaultValue: "true", ItemType: &BoolType{}, Editable: true, Description: `Enable notification`}, + {Name: common.MetricEnable, Scope: SystemScope, Group: BasicGroup, EnvKey: "METRIC_ENABLE", DefaultValue: "false", ItemType: &BoolType{}, Editable: true}, - {Name: common.MetricPort, Scope: SystemScope, Group: BasicGroup, EnvKey: "METRIC_PORT", DefaultValue: "9090", ItemType: &IntType{}, Editable: true}, + {Name: common.MetricPort, Scope: SystemScope, Group: BasicGroup, EnvKey: "METRIC_PORT", DefaultValue: "9090", ItemType: &PortType{}, Editable: true}, {Name: common.MetricPath, Scope: SystemScope, Group: BasicGroup, EnvKey: "METRIC_PATH", DefaultValue: "/metrics", ItemType: &StringType{}, Editable: true}, {Name: common.QuotaPerProjectEnable, Scope: UserScope, Group: QuotaGroup, EnvKey: "QUOTA_PER_PROJECT_ENABLE", DefaultValue: "true", ItemType: &BoolType{}, Editable: true, Description: `Enable quota per project`}, {Name: common.StoragePerProject, Scope: UserScope, Group: QuotaGroup, EnvKey: "STORAGE_PER_PROJECT", DefaultValue: "-1", ItemType: &QuotaType{}, Editable: true, Description: `The storage quota per project`}, + + {Name: common.TraceEnabled, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_ENABLED", DefaultValue: "false", ItemType: &BoolType{}, Editable: false, Description: `Enable trace`}, + {Name: common.TraceServiceName, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_SERVICE_NAME", DefaultValue: "", ItemType: &StringType{}, Editable: false, Description: `The service name of the trace`}, + {Name: common.TraceNamespace, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_NAMESPACE", DefaultValue: "", ItemType: &StringType{}, Editable: false, Description: `The namespace of the trace`}, + {Name: common.TraceSampleRate, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_SAMPLE_RATE", DefaultValue: "1", ItemType: &Float64Type{}, Editable: false, Description: `The sample rate of the trace`}, + {Name: common.TraceAttributes, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_ATTRIBUTES", DefaultValue: "", ItemType: &StringToStringMapType{}, Editable: false, Description: `The attribute of the trace`}, + {Name: common.TraceJaegerEndpoint, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_JAEGER_ENDPOINT", DefaultValue: "", ItemType: &StringType{}, Editable: false, Description: `The endpoint of the Jaeger`}, + {Name: common.TraceJaegerUsername, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_JAEGER_USERNAME", DefaultValue: "", ItemType: &StringType{}, Editable: false, Description: `The username of the Jaeger`}, + {Name: common.TraceJaegerPassword, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_JAEGER_PASSWORD", DefaultValue: "", ItemType: &PasswordType{}, Editable: false, Description: `The password of the Jaeger`}, + {Name: common.TraceJaegerAgentHost, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_JAEGER_AGENT_HOSTNAME", DefaultValue: "", ItemType: &StringType{}, Editable: false, Description: `The agent host of the Jaeger`}, + {Name: common.TraceJaegerAgentPort, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_JAEGER_AGENT_PORT", DefaultValue: "6831", ItemType: &StringType{}, Editable: false, Description: `The agent port of the Jaeger`}, + {Name: common.TraceOtelEndpoint, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_OTEL_ENDPOINT", DefaultValue: "", ItemType: &StringType{}, Editable: false, Description: `The endpoint of the Otel`}, + {Name: common.TraceOtelURLPath, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_OTEL_URL_PATH", DefaultValue: "", ItemType: &StringType{}, Editable: false, Description: `The URL path of the Otel`}, + {Name: common.TraceOtelCompression, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_OTEL_COMPRESSION", DefaultValue: "", ItemType: &BoolType{}, Editable: false, Description: `The compression of the Otel`}, + {Name: common.TraceOtelInsecure, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_OTEL_INSECURE", DefaultValue: "", ItemType: &BoolType{}, Editable: false, Description: `The insecure of the Otel`}, + {Name: common.TraceOtelTimeout, Scope: SystemScope, Group: BasicGroup, EnvKey: "TRACE_OTEL_TIMEOUT", DefaultValue: "", ItemType: &IntType{}, Editable: false, Description: `The timeout of the Otel`}, } ) diff --git a/src/lib/config/metadata/type.go b/src/lib/config/metadata/type.go index f6e38a2f8..b5a7dac64 100644 --- a/src/lib/config/metadata/type.go +++ b/src/lib/config/metadata/type.go @@ -148,6 +148,17 @@ func (t *Int64Type) get(str string) (interface{}, error) { return parseInt64(str) } +type Float64Type struct{} + +func (f *Float64Type) validate(str string) error { + _, err := parseFloat64(str) + return err +} + +func (f *Float64Type) get(str string) (interface{}, error) { + return parseFloat64(str) +} + // BoolType ... type BoolType struct { } @@ -251,3 +262,12 @@ func parseInt(str string) (int, error) { return 0, fmt.Errorf("invalid int string: %s", str) } + +func parseFloat64(str string) (float64, error) { + val, err := strconv.ParseFloat(str, 64) + if err == nil { + return val, nil + } + + return 0, fmt.Errorf("invalid float64 string: %s", str) +} diff --git a/src/lib/config/metadata/value.go b/src/lib/config/metadata/value.go index 7d268faca..68e00a79b 100644 --- a/src/lib/config/metadata/value.go +++ b/src/lib/config/metadata/value.go @@ -95,6 +95,21 @@ func (c *ConfigureValue) GetInt64() int64 { return 0 } +func (c *ConfigureValue) GetFloat64() float64 { + if item, ok := Instance().GetByName(c.Name); ok { + val, err := item.ItemType.get(c.Value) + if err != nil { + log.Errorf("GetFloat64 failed, error: %+v", err) + return 0 + } + if float64Value, suc := val.(float64); suc { + return float64Value + } + } + log.Errorf("GetFloat64 failed, the current value's metadata is not defined, %+v", c) + return 0 +} + // GetBool - return the bool value of current setting func (c *ConfigureValue) GetBool() bool { if item, ok := Instance().GetByName(c.Name); ok { diff --git a/src/lib/config/trace.go b/src/lib/config/trace.go new file mode 100644 index 000000000..b5c951fff --- /dev/null +++ b/src/lib/config/trace.go @@ -0,0 +1,37 @@ +package config + +import ( + "context" + + "github.com/goharbor/harbor/src/common" + commonhttp "github.com/goharbor/harbor/src/common/http" + "github.com/goharbor/harbor/src/lib/log" + tracelib "github.com/goharbor/harbor/src/lib/trace" +) + +func InitTraceConfig(ctx context.Context) { + cfgMgr, err := GetManager(common.InMemoryCfgManager) + if err != nil { + log.Fatalf("failed to get config manager: %v", err) + } + tracelib.InitGlobalConfig( + tracelib.WithEnabled(cfgMgr.Get(ctx, common.TraceEnabled).GetBool()), + tracelib.WithServiceName(cfgMgr.Get(ctx, common.TraceServiceName).GetString()), + tracelib.WithNamespace(cfgMgr.Get(ctx, common.TraceNamespace).GetString()), + tracelib.WithSampleRate(cfgMgr.Get(ctx, common.TraceSampleRate).GetFloat64()), + tracelib.WithAttributes(cfgMgr.Get(ctx, common.TraceAttributes).GetStringToStringMap()), + tracelib.WithJaegerEndpoint(cfgMgr.Get(ctx, common.TraceJaegerEndpoint).GetString()), + tracelib.WithJaegerUsername(cfgMgr.Get(ctx, common.TraceJaegerUsername).GetString()), + tracelib.WithJaegerPassword(cfgMgr.Get(ctx, common.TraceJaegerPassword).GetString()), + tracelib.WithJaegerAgentHost(cfgMgr.Get(ctx, common.TraceJaegerAgentHost).GetString()), + tracelib.WithJaegerAgentPort(cfgMgr.Get(ctx, common.TraceJaegerAgentPort).GetString()), + tracelib.WithOtelEndpoint(cfgMgr.Get(ctx, common.TraceOtelEndpoint).GetString()), + tracelib.WithOtelURLPath(cfgMgr.Get(ctx, common.TraceOtelURLPath).GetString()), + tracelib.WithOtelCompression(cfgMgr.Get(ctx, common.TraceOtelCompression).GetBool()), + tracelib.WithOtelInsecure(cfgMgr.Get(ctx, common.TraceOtelInsecure).GetBool()), + tracelib.WithOtelTimeout(cfgMgr.Get(ctx, common.TraceOtelTimeout).GetInt()), + ) + if tracelib.Enabled() { + commonhttp.AddTracingWithGlobalTransport() + } +} diff --git a/src/lib/trace/config.go b/src/lib/trace/config.go index 3817fa488..8c69db1e8 100644 --- a/src/lib/trace/config.go +++ b/src/lib/trace/config.go @@ -15,12 +15,7 @@ package trace import ( - "bytes" "fmt" - "strings" - - "github.com/goharbor/harbor/src/lib/log" - "github.com/spf13/viper" ) const ( @@ -30,31 +25,8 @@ const ( // C is the global configuration for trace var C Config -func init() { - viper.SetConfigType("json") - viper.SetEnvPrefix(TraceEnvPrefix) - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viper.AutomaticEnv() - C = Config{Otel: OtelConfig{}, Jaeger: JaegerConfig{}} - C.Enabled = viper.GetBool("enabled") - C.SampleRate = viper.GetFloat64("sample_rate") - C.Namespace = viper.GetString("namespace") - C.ServiceName = viper.GetString("service_name") - C.Jaeger.Endpoint = viper.GetString("jaeger_endpoint") - C.Jaeger.Username = viper.GetString("jaeger_agent_username") - C.Jaeger.Password = viper.GetString("jaeger_agent_password") - C.Jaeger.AgentHost = viper.GetString("jaeger_agent_host") - C.Jaeger.AgentPort = viper.GetString("jaeger_agent_port") - C.Otel.Endpoint = viper.GetString("otel_endpoint") - C.Otel.URLPath = viper.GetString("otel_url_path") - C.Otel.Compression = viper.GetBool("otel_compression") - C.Otel.Insecure = viper.GetBool("otel_insecure") - C.Otel.Timeout = viper.GetInt("otel_timeout") - var jsonExample = []byte(viper.GetString("attributes")) - viper.ReadConfig(bytes.NewBuffer(jsonExample)) - fmt.Println(viper.GetStringMapString("attributes")) - C.Attributes = viper.GetStringMapString("attributes") - log.Infof("ns: %s attr %+v", C.Namespace, C.Attributes) +func InitGlobalConfig(opts ...Option) { + C = NewConfig(opts...) } // OtelConfig is the configuration for otel @@ -66,6 +38,11 @@ type OtelConfig struct { Timeout int `mapstructure:"otel_trace_timeout"` } +func (c *OtelConfig) String() string { + return fmt.Sprintf("endpoint: %s, url_path: %s, compression: %t, insecure: %t, timeout: %d", + c.Endpoint, c.URLPath, c.Compression, c.Insecure, c.Timeout) +} + // JaegerConfig is the configuration for Jaeger type JaegerConfig struct { Endpoint string `mapstructure:"jaeger_endpoint"` @@ -75,6 +52,11 @@ type JaegerConfig struct { AgentPort string `mapstructure:"jaeger_agent_port"` } +func (c *JaegerConfig) String() string { + return fmt.Sprintf("endpoint: %s, username: %s, password: %s, agent_host: %s, agent_port: %s", + c.Endpoint, c.Username, c.Password, c.AgentHost, c.AgentPort) +} + // Config is the configuration for trace type Config struct { Enabled bool `mapstructure:"enabled"` @@ -86,8 +68,111 @@ type Config struct { Attributes map[string]string } +func (c *Config) String() string { + return fmt.Sprintf("{Enabled: %v, ServiceName: %v, SampleRate: %v, Namespace: %v, ServiceName: %v, Jaeger: %v, Otel: %v}", c.Enabled, c.ServiceName, c.SampleRate, c.Namespace, c.ServiceName, c.Jaeger, c.Otel) +} + +type Option func(*Config) + +func WithEnabled(enabled bool) Option { + return func(c *Config) { + c.Enabled = enabled + } +} + +func WithSampleRate(sampleRate float64) Option { + return func(c *Config) { + c.SampleRate = sampleRate + } +} + +func WithNamespace(namespace string) Option { + return func(c *Config) { + c.Namespace = namespace + } +} + +func WithServiceName(serviceName string) Option { + return func(c *Config) { + c.ServiceName = serviceName + } +} + +func WithAttributes(attributes map[string]string) Option { + return func(c *Config) { + c.Attributes = attributes + } +} + +func WithJaegerEndpoint(endpoint string) Option { + return func(c *Config) { + c.Jaeger.Endpoint = endpoint + } +} + +func WithJaegerUsername(username string) Option { + return func(c *Config) { + c.Jaeger.Username = username + } +} + +func WithJaegerPassword(password string) Option { + return func(c *Config) { + c.Jaeger.Password = password + } +} + +func WithJaegerAgentHost(host string) Option { + return func(c *Config) { + c.Jaeger.AgentHost = host + } +} +func WithJaegerAgentPort(port string) Option { + return func(c *Config) { + c.Jaeger.AgentPort = port + } +} + +func WithOtelEndpoint(endpoint string) Option { + return func(c *Config) { + c.Otel.Endpoint = endpoint + } +} + +func WithOtelURLPath(urlPath string) Option { + return func(c *Config) { + c.Otel.URLPath = urlPath + } +} + +func WithOtelCompression(compression bool) Option { + return func(c *Config) { + c.Otel.Compression = compression + } +} + +func WithOtelInsecure(insecure bool) Option { + return func(c *Config) { + c.Otel.Insecure = insecure + } +} + +func WithOtelTimeout(timeout int) Option { + return func(c *Config) { + c.Otel.Timeout = timeout + } +} + +func NewConfig(opts ...Option) Config { + c := Config{Otel: OtelConfig{}, Jaeger: JaegerConfig{}} + for _, opt := range opts { + opt(&c) + } + return c +} + // GetConfig returns the global configuration for trace -func GetConfig() Config { +func GetGlobalConfig() Config { return C } diff --git a/src/lib/trace/trace.go b/src/lib/trace/trace.go index 9ff7f94c3..9d561b863 100644 --- a/src/lib/trace/trace.go +++ b/src/lib/trace/trace.go @@ -35,7 +35,7 @@ import ( func initExporter(ctx context.Context) (tracesdk.SpanExporter, error) { var err error var exp tracesdk.SpanExporter - cfg := GetConfig() + cfg := GetGlobalConfig() if len(cfg.Jaeger.Endpoint) != 0 { // Jaeger collector exporter log.Infof("init trace provider jaeger collector on %s with user %s", cfg.Jaeger.Endpoint, cfg.Jaeger.Username) @@ -73,12 +73,15 @@ func initExporter(ctx context.Context) (tracesdk.SpanExporter, error) { } func initProvider(exp tracesdk.SpanExporter) (*tracesdk.TracerProvider, error) { - cfg := GetConfig() + cfg := GetGlobalConfig() // prepare attribute resources attriSlice := []attribute.KeyValue{ semconv.ServiceNameKey.String(cfg.ServiceName), - semconv.ServiceVersionKey.String(version.ReleaseVersion)} + } + if len(version.ReleaseVersion) != 0 { + attriSlice = append(attriSlice, semconv.ServiceVersionKey.String(version.ReleaseVersion)) + } if cfg.Namespace != "" { attriSlice = append(attriSlice, semconv.ServiceNamespaceKey.String(cfg.Namespace)) } diff --git a/src/registryctl/main.go b/src/registryctl/main.go index 1607e3a95..f533334f2 100644 --- a/src/registryctl/main.go +++ b/src/registryctl/main.go @@ -35,8 +35,10 @@ import ( _ "github.com/docker/distribution/registry/storage/driver/swift" common_http "github.com/goharbor/harbor/src/common/http" + cfgLib "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/log" tracelib "github.com/goharbor/harbor/src/lib/trace" + _ "github.com/goharbor/harbor/src/pkg/config/inmemory" "github.com/goharbor/harbor/src/registryctl/config" "github.com/goharbor/harbor/src/registryctl/handlers" ) @@ -83,7 +85,6 @@ func (s *RegistryCtl) Start() { if err != nil { log.Fatal(err) } - return } func main() { @@ -97,6 +98,9 @@ func main() { if err := config.DefaultConfig.Load(*configPath, true); err != nil { log.Fatalf("Failed to load configurations with error: %s\n", err) } + + cfgLib.InitTraceConfig(context.Background()) + regCtl := &RegistryCtl{ ServerConf: *config.DefaultConfig, Handler: handlers.NewHandlerChain(*config.DefaultConfig),