// Package config loads the authsession process configuration from environment // variables. package config import ( "fmt" "os" "strconv" "strings" "time" "galaxy/authsession/internal/api/internalhttp" "galaxy/authsession/internal/api/publichttp" "go.uber.org/zap/zapcore" ) const ( shutdownTimeoutEnvVar = "AUTHSESSION_SHUTDOWN_TIMEOUT" logLevelEnvVar = "AUTHSESSION_LOG_LEVEL" publicHTTPAddrEnvVar = "AUTHSESSION_PUBLIC_HTTP_ADDR" publicHTTPReadHeaderTimeoutEnvVar = "AUTHSESSION_PUBLIC_HTTP_READ_HEADER_TIMEOUT" publicHTTPReadTimeoutEnvVar = "AUTHSESSION_PUBLIC_HTTP_READ_TIMEOUT" publicHTTPIdleTimeoutEnvVar = "AUTHSESSION_PUBLIC_HTTP_IDLE_TIMEOUT" publicHTTPRequestTimeoutEnvVar = "AUTHSESSION_PUBLIC_HTTP_REQUEST_TIMEOUT" internalHTTPAddrEnvVar = "AUTHSESSION_INTERNAL_HTTP_ADDR" internalHTTPReadHeaderTimeoutEnvVar = "AUTHSESSION_INTERNAL_HTTP_READ_HEADER_TIMEOUT" internalHTTPReadTimeoutEnvVar = "AUTHSESSION_INTERNAL_HTTP_READ_TIMEOUT" internalHTTPIdleTimeoutEnvVar = "AUTHSESSION_INTERNAL_HTTP_IDLE_TIMEOUT" internalHTTPRequestTimeoutEnvVar = "AUTHSESSION_INTERNAL_HTTP_REQUEST_TIMEOUT" redisAddrEnvVar = "AUTHSESSION_REDIS_ADDR" redisUsernameEnvVar = "AUTHSESSION_REDIS_USERNAME" redisPasswordEnvVar = "AUTHSESSION_REDIS_PASSWORD" redisDBEnvVar = "AUTHSESSION_REDIS_DB" redisTLSEnabledEnvVar = "AUTHSESSION_REDIS_TLS_ENABLED" redisOperationTimeoutEnvVar = "AUTHSESSION_REDIS_OPERATION_TIMEOUT" redisChallengeKeyPrefixEnvVar = "AUTHSESSION_REDIS_CHALLENGE_KEY_PREFIX" redisSessionKeyPrefixEnvVar = "AUTHSESSION_REDIS_SESSION_KEY_PREFIX" redisUserSessionsKeyPrefixEnvVar = "AUTHSESSION_REDIS_USER_SESSIONS_KEY_PREFIX" redisUserActiveSessionsKeyPrefixEnvVar = "AUTHSESSION_REDIS_USER_ACTIVE_SESSIONS_KEY_PREFIX" redisSessionLimitKeyEnvVar = "AUTHSESSION_REDIS_SESSION_LIMIT_KEY" redisGatewaySessionCacheKeyPrefixEnvVar = "AUTHSESSION_REDIS_GATEWAY_SESSION_CACHE_KEY_PREFIX" redisGatewaySessionEventsStreamEnvVar = "AUTHSESSION_REDIS_GATEWAY_SESSION_EVENTS_STREAM" redisGatewaySessionEventsStreamMaxLenEnvVar = "AUTHSESSION_REDIS_GATEWAY_SESSION_EVENTS_STREAM_MAX_LEN" redisSendEmailCodeThrottleKeyPrefixEnvVar = "AUTHSESSION_REDIS_SEND_EMAIL_CODE_THROTTLE_KEY_PREFIX" userServiceModeEnvVar = "AUTHSESSION_USER_SERVICE_MODE" userServiceBaseURLEnvVar = "AUTHSESSION_USER_SERVICE_BASE_URL" userServiceRequestTimeoutEnvVar = "AUTHSESSION_USER_SERVICE_REQUEST_TIMEOUT" mailServiceModeEnvVar = "AUTHSESSION_MAIL_SERVICE_MODE" mailServiceBaseURLEnvVar = "AUTHSESSION_MAIL_SERVICE_BASE_URL" mailServiceRequestTimeoutEnvVar = "AUTHSESSION_MAIL_SERVICE_REQUEST_TIMEOUT" otelServiceNameEnvVar = "OTEL_SERVICE_NAME" otelTracesExporterEnvVar = "OTEL_TRACES_EXPORTER" otelMetricsExporterEnvVar = "OTEL_METRICS_EXPORTER" otelExporterOTLPProtocolEnvVar = "OTEL_EXPORTER_OTLP_PROTOCOL" otelExporterOTLPTracesProtocolEnvVar = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL" otelExporterOTLPMetricsProtocolEnvVar = "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL" otelStdoutTracesEnabledEnvVar = "AUTHSESSION_OTEL_STDOUT_TRACES_ENABLED" otelStdoutMetricsEnabledEnvVar = "AUTHSESSION_OTEL_STDOUT_METRICS_ENABLED" defaultShutdownTimeout = 5 * time.Second defaultLogLevel = "info" defaultRedisDB = 0 defaultRedisOperationTimeout = 250 * time.Millisecond defaultChallengeKeyPrefix = "authsession:challenge:" defaultSessionKeyPrefix = "authsession:session:" defaultUserSessionsKeyPrefix = "authsession:user-sessions:" defaultUserActiveSessionsKeyPrefix = "authsession:user-active-sessions:" defaultSessionLimitKey = "authsession:config:active-session-limit" defaultGatewaySessionCacheKeyPrefix = "gateway:session:" defaultGatewaySessionEventsStream = "gateway:session_events" defaultGatewaySessionEventsStreamMaxLen = 1024 defaultSendEmailCodeThrottleKeyPrefix = "authsession:send-email-code-throttle:" defaultUserServiceMode = userServiceModeStub defaultUserServiceRequestTimeout = time.Second defaultMailServiceMode = mailServiceModeStub defaultMailServiceRequestTimeout = time.Second defaultOTelServiceName = "galaxy-authsession" otelExporterNone = "none" otelExporterOTLP = "otlp" otelProtocolHTTPProtobuf = "http/protobuf" otelProtocolGRPC = "grpc" userServiceModeStub = "stub" userServiceModeREST = "rest" mailServiceModeStub = "stub" mailServiceModeREST = "rest" ) // Config stores the full process-level authsession configuration. type Config struct { // ShutdownTimeout bounds graceful shutdown of every long-lived component. ShutdownTimeout time.Duration // Logging configures the process-wide structured logger. Logging LoggingConfig // PublicHTTP configures the public HTTP listener. PublicHTTP publichttp.Config // InternalHTTP configures the trusted internal HTTP listener. InternalHTTP internalhttp.Config // Redis configures the Redis-backed adapters. Redis RedisConfig // UserService configures the selectable runtime user-directory adapter. UserService UserServiceConfig // MailService configures the selectable runtime mail-delivery adapter. MailService MailServiceConfig // Telemetry configures the process-wide OpenTelemetry runtime. Telemetry TelemetryConfig } // LoggingConfig configures the process-wide structured logger. type LoggingConfig struct { // Level stores the zap-compatible log level string. Level string } // RedisConfig configures the Redis-backed authsession adapters. type RedisConfig struct { // Addr is the shared Redis address used by the authsession adapters. Addr string // Username is the optional Redis ACL username. Username string // Password is the optional Redis ACL password. Password string // DB is the Redis logical database index. DB int // TLSEnabled configures whether Redis connections use TLS. TLSEnabled bool // OperationTimeout bounds each adapter Redis round trip. OperationTimeout time.Duration // ChallengeKeyPrefix namespaces the challenge source-of-truth records. ChallengeKeyPrefix string // SessionKeyPrefix namespaces the primary session records. SessionKeyPrefix string // UserSessionsKeyPrefix namespaces the all-session user index. UserSessionsKeyPrefix string // UserActiveSessionsKeyPrefix namespaces the active-session user index. UserActiveSessionsKeyPrefix string // SessionLimitKey stores the exact session-limit Redis key. SessionLimitKey string // GatewaySessionCacheKeyPrefix namespaces the projected gateway session // cache keys. GatewaySessionCacheKeyPrefix string // GatewaySessionEventsStream stores the projected gateway session-events // Redis Stream key. GatewaySessionEventsStream string // GatewaySessionEventsStreamMaxLen bounds the projected gateway session // event stream with approximate trimming. GatewaySessionEventsStreamMaxLen int64 // SendEmailCodeThrottleKeyPrefix namespaces the resend-throttle TTL keys. SendEmailCodeThrottleKeyPrefix string } // UserServiceConfig configures the runtime user-directory integration mode. type UserServiceConfig struct { // Mode selects the runtime adapter implementation. Supported values are // `stub` and `rest`. Mode string // BaseURL is the absolute base URL of the REST-backed user-service when // Mode is `rest`. BaseURL string // RequestTimeout bounds each outbound user-service request when Mode is // `rest`. RequestTimeout time.Duration } // MailServiceConfig configures the runtime mail-delivery integration mode. type MailServiceConfig struct { // Mode selects the runtime adapter implementation. Supported values are // `stub` and `rest`. Mode string // BaseURL is the absolute base URL of the REST-backed mail service when // Mode is `rest`. BaseURL string // RequestTimeout bounds each outbound mail-service request when Mode is // `rest`. RequestTimeout time.Duration } // TelemetryConfig configures the authsession OpenTelemetry runtime. type TelemetryConfig struct { // ServiceName overrides the default OpenTelemetry service name. ServiceName string // TracesExporter selects the external traces exporter. Supported values are // `none` and `otlp`. TracesExporter string // MetricsExporter selects the external metrics exporter. Supported values // are `none` and `otlp`. MetricsExporter string // TracesProtocol selects the OTLP traces protocol when TracesExporter is // `otlp`. TracesProtocol string // MetricsProtocol selects the OTLP metrics protocol when MetricsExporter is // `otlp`. MetricsProtocol string // StdoutTracesEnabled enables the additional stdout trace exporter used for // local development and debugging. StdoutTracesEnabled bool // StdoutMetricsEnabled enables the additional stdout metric exporter used // for local development and debugging. StdoutMetricsEnabled bool } // DefaultConfig returns the default authsession process configuration with all // optional values filled. func DefaultConfig() Config { return Config{ ShutdownTimeout: defaultShutdownTimeout, Logging: LoggingConfig{ Level: defaultLogLevel, }, PublicHTTP: publichttp.DefaultConfig(), InternalHTTP: internalhttp.DefaultConfig(), Redis: RedisConfig{ DB: defaultRedisDB, OperationTimeout: defaultRedisOperationTimeout, ChallengeKeyPrefix: defaultChallengeKeyPrefix, SessionKeyPrefix: defaultSessionKeyPrefix, UserSessionsKeyPrefix: defaultUserSessionsKeyPrefix, UserActiveSessionsKeyPrefix: defaultUserActiveSessionsKeyPrefix, SessionLimitKey: defaultSessionLimitKey, GatewaySessionCacheKeyPrefix: defaultGatewaySessionCacheKeyPrefix, GatewaySessionEventsStream: defaultGatewaySessionEventsStream, GatewaySessionEventsStreamMaxLen: defaultGatewaySessionEventsStreamMaxLen, SendEmailCodeThrottleKeyPrefix: defaultSendEmailCodeThrottleKeyPrefix, }, UserService: UserServiceConfig{ Mode: defaultUserServiceMode, RequestTimeout: defaultUserServiceRequestTimeout, }, MailService: MailServiceConfig{ Mode: defaultMailServiceMode, RequestTimeout: defaultMailServiceRequestTimeout, }, Telemetry: TelemetryConfig{ ServiceName: defaultOTelServiceName, TracesExporter: otelExporterNone, MetricsExporter: otelExporterNone, }, } } // LoadFromEnv loads the authsession process configuration from environment // variables, applying documented defaults where appropriate. func LoadFromEnv() (Config, error) { cfg := DefaultConfig() var err error cfg.ShutdownTimeout, err = loadDurationEnvWithDefault(shutdownTimeoutEnvVar, cfg.ShutdownTimeout) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.Logging.Level = loadStringEnvWithDefault(logLevelEnvVar, cfg.Logging.Level) if err := validateLogLevel(cfg.Logging.Level); err != nil { return Config{}, fmt.Errorf("load authsession config: %s: %w", logLevelEnvVar, err) } cfg.PublicHTTP.Addr = loadStringEnvWithDefault(publicHTTPAddrEnvVar, cfg.PublicHTTP.Addr) cfg.PublicHTTP.ReadHeaderTimeout, err = loadDurationEnvWithDefault(publicHTTPReadHeaderTimeoutEnvVar, cfg.PublicHTTP.ReadHeaderTimeout) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.PublicHTTP.ReadTimeout, err = loadDurationEnvWithDefault(publicHTTPReadTimeoutEnvVar, cfg.PublicHTTP.ReadTimeout) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.PublicHTTP.IdleTimeout, err = loadDurationEnvWithDefault(publicHTTPIdleTimeoutEnvVar, cfg.PublicHTTP.IdleTimeout) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.PublicHTTP.RequestTimeout, err = loadDurationEnvWithDefault(publicHTTPRequestTimeoutEnvVar, cfg.PublicHTTP.RequestTimeout) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.InternalHTTP.Addr = loadStringEnvWithDefault(internalHTTPAddrEnvVar, cfg.InternalHTTP.Addr) cfg.InternalHTTP.ReadHeaderTimeout, err = loadDurationEnvWithDefault(internalHTTPReadHeaderTimeoutEnvVar, cfg.InternalHTTP.ReadHeaderTimeout) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.InternalHTTP.ReadTimeout, err = loadDurationEnvWithDefault(internalHTTPReadTimeoutEnvVar, cfg.InternalHTTP.ReadTimeout) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.InternalHTTP.IdleTimeout, err = loadDurationEnvWithDefault(internalHTTPIdleTimeoutEnvVar, cfg.InternalHTTP.IdleTimeout) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.InternalHTTP.RequestTimeout, err = loadDurationEnvWithDefault(internalHTTPRequestTimeoutEnvVar, cfg.InternalHTTP.RequestTimeout) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.Redis.Addr = loadStringEnvWithDefault(redisAddrEnvVar, cfg.Redis.Addr) cfg.Redis.Username = os.Getenv(redisUsernameEnvVar) cfg.Redis.Password = os.Getenv(redisPasswordEnvVar) cfg.Redis.DB, err = loadIntEnvWithDefault(redisDBEnvVar, cfg.Redis.DB) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.Redis.TLSEnabled, err = loadBoolEnvWithDefault(redisTLSEnabledEnvVar, cfg.Redis.TLSEnabled) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.Redis.OperationTimeout, err = loadDurationEnvWithDefault(redisOperationTimeoutEnvVar, cfg.Redis.OperationTimeout) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.Redis.ChallengeKeyPrefix = loadStringEnvWithDefault(redisChallengeKeyPrefixEnvVar, cfg.Redis.ChallengeKeyPrefix) cfg.Redis.SessionKeyPrefix = loadStringEnvWithDefault(redisSessionKeyPrefixEnvVar, cfg.Redis.SessionKeyPrefix) cfg.Redis.UserSessionsKeyPrefix = loadStringEnvWithDefault(redisUserSessionsKeyPrefixEnvVar, cfg.Redis.UserSessionsKeyPrefix) cfg.Redis.UserActiveSessionsKeyPrefix = loadStringEnvWithDefault(redisUserActiveSessionsKeyPrefixEnvVar, cfg.Redis.UserActiveSessionsKeyPrefix) cfg.Redis.SessionLimitKey = loadStringEnvWithDefault(redisSessionLimitKeyEnvVar, cfg.Redis.SessionLimitKey) cfg.Redis.GatewaySessionCacheKeyPrefix = loadStringEnvWithDefault(redisGatewaySessionCacheKeyPrefixEnvVar, cfg.Redis.GatewaySessionCacheKeyPrefix) cfg.Redis.GatewaySessionEventsStream = loadStringEnvWithDefault(redisGatewaySessionEventsStreamEnvVar, cfg.Redis.GatewaySessionEventsStream) streamMaxLen, err := loadInt64EnvWithDefault(redisGatewaySessionEventsStreamMaxLenEnvVar, cfg.Redis.GatewaySessionEventsStreamMaxLen) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.Redis.GatewaySessionEventsStreamMaxLen = streamMaxLen cfg.Redis.SendEmailCodeThrottleKeyPrefix = loadStringEnvWithDefault(redisSendEmailCodeThrottleKeyPrefixEnvVar, cfg.Redis.SendEmailCodeThrottleKeyPrefix) cfg.UserService.Mode = strings.TrimSpace(loadStringEnvWithDefault(userServiceModeEnvVar, cfg.UserService.Mode)) cfg.UserService.BaseURL = loadStringEnvWithDefault(userServiceBaseURLEnvVar, cfg.UserService.BaseURL) cfg.UserService.RequestTimeout, err = loadDurationEnvWithDefault(userServiceRequestTimeoutEnvVar, cfg.UserService.RequestTimeout) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.MailService.Mode = strings.TrimSpace(loadStringEnvWithDefault(mailServiceModeEnvVar, cfg.MailService.Mode)) cfg.MailService.BaseURL = loadStringEnvWithDefault(mailServiceBaseURLEnvVar, cfg.MailService.BaseURL) cfg.MailService.RequestTimeout, err = loadDurationEnvWithDefault(mailServiceRequestTimeoutEnvVar, cfg.MailService.RequestTimeout) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.Telemetry.ServiceName = loadStringEnvWithDefault(otelServiceNameEnvVar, cfg.Telemetry.ServiceName) cfg.Telemetry.TracesExporter = normalizeExporterValue(loadStringEnvWithDefault(otelTracesExporterEnvVar, cfg.Telemetry.TracesExporter)) cfg.Telemetry.MetricsExporter = normalizeExporterValue(loadStringEnvWithDefault(otelMetricsExporterEnvVar, cfg.Telemetry.MetricsExporter)) cfg.Telemetry.TracesProtocol = loadOTLPProtocol( os.Getenv(otelExporterOTLPTracesProtocolEnvVar), os.Getenv(otelExporterOTLPProtocolEnvVar), cfg.Telemetry.TracesExporter, ) cfg.Telemetry.MetricsProtocol = loadOTLPProtocol( os.Getenv(otelExporterOTLPMetricsProtocolEnvVar), os.Getenv(otelExporterOTLPProtocolEnvVar), cfg.Telemetry.MetricsExporter, ) cfg.Telemetry.StdoutTracesEnabled, err = loadBoolEnvWithDefault(otelStdoutTracesEnabledEnvVar, cfg.Telemetry.StdoutTracesEnabled) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } cfg.Telemetry.StdoutMetricsEnabled, err = loadBoolEnvWithDefault(otelStdoutMetricsEnabledEnvVar, cfg.Telemetry.StdoutMetricsEnabled) if err != nil { return Config{}, fmt.Errorf("load authsession config: %w", err) } if err := cfg.Validate(); err != nil { return Config{}, err } return cfg, nil } // Validate reports whether cfg contains a consistent authsession process // configuration. func (cfg Config) Validate() error { switch { case cfg.ShutdownTimeout <= 0: return fmt.Errorf("load authsession config: %s must be positive", shutdownTimeoutEnvVar) case strings.TrimSpace(cfg.Redis.Addr) == "": return fmt.Errorf("load authsession config: %s must not be empty", redisAddrEnvVar) case cfg.Redis.DB < 0: return fmt.Errorf("load authsession config: %s must not be negative", redisDBEnvVar) case cfg.Redis.OperationTimeout <= 0: return fmt.Errorf("load authsession config: %s must be positive", redisOperationTimeoutEnvVar) case strings.TrimSpace(cfg.Redis.ChallengeKeyPrefix) == "": return fmt.Errorf("load authsession config: %s must not be empty", redisChallengeKeyPrefixEnvVar) case strings.TrimSpace(cfg.Redis.SessionKeyPrefix) == "": return fmt.Errorf("load authsession config: %s must not be empty", redisSessionKeyPrefixEnvVar) case strings.TrimSpace(cfg.Redis.UserSessionsKeyPrefix) == "": return fmt.Errorf("load authsession config: %s must not be empty", redisUserSessionsKeyPrefixEnvVar) case strings.TrimSpace(cfg.Redis.UserActiveSessionsKeyPrefix) == "": return fmt.Errorf("load authsession config: %s must not be empty", redisUserActiveSessionsKeyPrefixEnvVar) case strings.TrimSpace(cfg.Redis.SessionLimitKey) == "": return fmt.Errorf("load authsession config: %s must not be empty", redisSessionLimitKeyEnvVar) case strings.TrimSpace(cfg.Redis.GatewaySessionCacheKeyPrefix) == "": return fmt.Errorf("load authsession config: %s must not be empty", redisGatewaySessionCacheKeyPrefixEnvVar) case strings.TrimSpace(cfg.Redis.GatewaySessionEventsStream) == "": return fmt.Errorf("load authsession config: %s must not be empty", redisGatewaySessionEventsStreamEnvVar) case cfg.Redis.GatewaySessionEventsStreamMaxLen <= 0: return fmt.Errorf("load authsession config: %s must be positive", redisGatewaySessionEventsStreamMaxLenEnvVar) case strings.TrimSpace(cfg.Redis.SendEmailCodeThrottleKeyPrefix) == "": return fmt.Errorf("load authsession config: %s must not be empty", redisSendEmailCodeThrottleKeyPrefixEnvVar) } if err := cfg.PublicHTTP.Validate(); err != nil { return fmt.Errorf("load authsession config: public HTTP: %w", err) } if err := cfg.InternalHTTP.Validate(); err != nil { return fmt.Errorf("load authsession config: internal HTTP: %w", err) } if err := cfg.UserService.Validate(); err != nil { return fmt.Errorf("load authsession config: %w", err) } if err := cfg.MailService.Validate(); err != nil { return fmt.Errorf("load authsession config: %w", err) } if err := cfg.Telemetry.Validate(); err != nil { return fmt.Errorf("load authsession config: %w", err) } return nil } // Validate reports whether cfg contains a supported user-service runtime // configuration. func (cfg UserServiceConfig) Validate() error { switch cfg.Mode { case userServiceModeStub: return nil case userServiceModeREST: if strings.TrimSpace(cfg.BaseURL) == "" { return fmt.Errorf("%s must not be empty in rest mode", userServiceBaseURLEnvVar) } if cfg.RequestTimeout <= 0 { return fmt.Errorf("%s must be positive in rest mode", userServiceRequestTimeoutEnvVar) } return nil default: return fmt.Errorf("%s %q is unsupported", userServiceModeEnvVar, cfg.Mode) } } // Validate reports whether cfg contains a supported mail-service runtime // configuration. func (cfg MailServiceConfig) Validate() error { switch cfg.Mode { case mailServiceModeStub: return nil case mailServiceModeREST: if strings.TrimSpace(cfg.BaseURL) == "" { return fmt.Errorf("%s must not be empty in rest mode", mailServiceBaseURLEnvVar) } if cfg.RequestTimeout <= 0 { return fmt.Errorf("%s must be positive in rest mode", mailServiceRequestTimeoutEnvVar) } return nil default: return fmt.Errorf("%s %q is unsupported", mailServiceModeEnvVar, cfg.Mode) } } // Validate reports whether cfg contains a supported OpenTelemetry exporter // configuration. func (cfg TelemetryConfig) Validate() error { switch cfg.TracesExporter { case otelExporterNone, otelExporterOTLP: default: return fmt.Errorf("%s %q is unsupported", otelTracesExporterEnvVar, cfg.TracesExporter) } switch cfg.MetricsExporter { case otelExporterNone, otelExporterOTLP: default: return fmt.Errorf("%s %q is unsupported", otelMetricsExporterEnvVar, cfg.MetricsExporter) } if cfg.TracesProtocol != "" && cfg.TracesProtocol != otelProtocolHTTPProtobuf && cfg.TracesProtocol != otelProtocolGRPC { return fmt.Errorf("%s %q is unsupported", otelExporterOTLPTracesProtocolEnvVar, cfg.TracesProtocol) } if cfg.MetricsProtocol != "" && cfg.MetricsProtocol != otelProtocolHTTPProtobuf && cfg.MetricsProtocol != otelProtocolGRPC { return fmt.Errorf("%s %q is unsupported", otelExporterOTLPMetricsProtocolEnvVar, cfg.MetricsProtocol) } return nil } func loadStringEnvWithDefault(name string, value string) string { if raw, ok := os.LookupEnv(name); ok { return strings.TrimSpace(raw) } return value } func loadDurationEnvWithDefault(name string, value time.Duration) (time.Duration, error) { raw, ok := os.LookupEnv(name) if !ok { return value, nil } parsed, err := time.ParseDuration(strings.TrimSpace(raw)) if err != nil { return 0, fmt.Errorf("%s: %w", name, err) } return parsed, nil } func loadIntEnvWithDefault(name string, value int) (int, error) { raw, ok := os.LookupEnv(name) if !ok { return value, nil } parsed, err := strconv.Atoi(strings.TrimSpace(raw)) if err != nil { return 0, fmt.Errorf("%s: %w", name, err) } return parsed, nil } func loadInt64EnvWithDefault(name string, value int64) (int64, error) { raw, ok := os.LookupEnv(name) if !ok { return value, nil } parsed, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64) if err != nil { return 0, fmt.Errorf("%s: %w", name, err) } return parsed, nil } func loadBoolEnvWithDefault(name string, value bool) (bool, error) { raw, ok := os.LookupEnv(name) if !ok { return value, nil } parsed, err := strconv.ParseBool(strings.TrimSpace(raw)) if err != nil { return false, fmt.Errorf("%s: %w", name, err) } return parsed, nil } func validateLogLevel(value string) error { var level zapcore.Level if err := level.UnmarshalText([]byte(strings.TrimSpace(value))); err != nil { return err } return nil } func normalizeExporterValue(value string) string { switch strings.TrimSpace(value) { case "", otelExporterNone: return otelExporterNone default: return strings.TrimSpace(value) } } func loadOTLPProtocol(primary string, fallback string, exporter string) string { protocol := strings.TrimSpace(primary) if protocol == "" { protocol = strings.TrimSpace(fallback) } if protocol == "" && exporter == otelExporterOTLP { return otelProtocolHTTPProtobuf } return protocol }