package tracing import ( "fmt" "os" "strconv" "strings" "github.com/juju/errors" "gopkg.in/yaml.v3" "git.dev.m-and-m.ovh/mderasse/gocommon/convert" "git.dev.m-and-m.ovh/mderasse/gocommon/tracing/exporter/jaeger" "git.dev.m-and-m.ovh/mderasse/gocommon/tracing/exporter/zipkin" ) // envPrefix is the prefix that will be used for any environnement variable used to configure the tracing system. const envPrefix = "TRACING_" // CONFIG_FILE is the default file that will be searched to apply configuration from the filesystem. const defaultConfigFile = "tracing.yaml" // SECRET_NAME is the default name of the secret that will be searched in vault. const defaultSecretName = "tracing" // ConfigStruct represent the configuration of our tracing system. type ConfigStruct struct { Attributes map[string]string `yaml:"attributes"` Enabled bool `yaml:"enabled"` Exporter ExporterName `yaml:"exporter"` ServiceName string `yaml:"service_name"` ServiceVersion string `yaml:"service_version"` ServiceInstanceID string `yaml:"service_instance_id"` JaegerConfig *jaeger.ConfigStruct `yaml:"jaeger_config"` ZipkinConfig *zipkin.ConfigStruct `yaml:"zipkin_config"` } func newDefaultConfig() *ConfigStruct { return &ConfigStruct{ Enabled: false, } } func loadConfigFromVault(secret string) (*ConfigStruct, error) { return nil, os.ErrNotExist } // loadConfigFromFile will read the given file and return a config struct. func loadConfigFromFile(path string) (*ConfigStruct, error) { if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { return nil, errors.Trace(err) } //nolint:gosec // we did compute the file path f, err := os.ReadFile(path) if err != nil { return nil, errors.Trace(err) } var config ConfigStruct if err := yaml.Unmarshal(f, &config); err != nil { return nil, errors.Trace(err) } return &config, nil } func loadConfig() (*ConfigStruct, error) { c, err := loadConfigFromVault(defaultSecretName) if err != nil && !errors.Is(err, os.ErrNotExist) { return nil, err } else if err == nil { return c, nil } c, err = loadConfigFromFile(defaultConfigFile) if err != nil && !errors.Is(err, os.ErrNotExist) { return nil, err } else if err == nil { return c, nil } return newDefaultConfig(), nil } // applyEnv will retrieve info from environment variable and overide those got by config / vault. // validity of the value is not checked here and will be check in a IsValid method. func (c *ConfigStruct) applyEnv() error { if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "ENABLED")); v != "" { b, err := strconv.ParseBool(v) if err != nil { return errors.NewNotValid(err, fmt.Sprintf("Invalid %s%s environment variable. Should be a boolean", envPrefix, "ENABLED")) } c.Enabled = b } if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "SERVICE_NAME")); v != "" { c.ServiceName = v } if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "SERVICE_VERSION")); v != "" { c.ServiceVersion = v } if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "SERVICE_INSTANCE_ID")); v != "" { c.ServiceInstanceID = v } if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "EXPORTER")); v != "" { c.Exporter = ExporterName(v) } // Attributes if v := os.Getenv(fmt.Sprintf("%s%s", envPrefix, "ATTRIBUTES")); v != "" { attributeParts := strings.Split(v, ",") for _, ap := range attributeParts { attributeKV := strings.SplitN(ap, ":", 1) if len(attributeKV) != 2 { return errors.NotValidf(fmt.Sprintf("Invalid attribute %s in environment variable. Should be a key1:value1,key2:value2 format", ap)) } c.Attributes[attributeKV[0]] = attributeKV[1] } } return nil } // IsValid will check a config struct. func (c *ConfigStruct) IsValid() error { if c.Enabled { if c.ServiceName == "" { return errors.NotValidf("ServiceName is empty") } if !c.Exporter.IsValid() { return errors.NotValidf("Invalid Exporter %s. Allowed values: %s", c.Exporter.String(), strings.Join(convert.StringerSliceToStringSlice(GetListExporterName()), ", ")) } switch c.Exporter { case ExporterName_JAEGER: if c.JaegerConfig == nil { return errors.NotValidf("jaeger configuration is empty") } err := c.JaegerConfig.IsValid() if err != nil { return errors.Trace(err) } case ExporterName_ZIPKIN: if c.ZipkinConfig == nil { return errors.NotValidf("zipkin configuration is empty") } err := c.ZipkinConfig.IsValid() if err != nil { return errors.Trace(err) } default: return errors.NotValidf("Exporter not handled") } } return nil }