Add OpenTelemetry instrumentation with middleware-only approach

This commit is contained in:
Mistral Vibe
2026-04-04 12:19:48 +02:00
committed by Gabriel Radureau
parent c38d7c6d76
commit 36f0b79b90
13 changed files with 452 additions and 46 deletions

View File

@@ -11,16 +11,41 @@ import (
// Config represents the application configuration
type Config struct {
Server struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
}
Shutdown struct {
Timeout time.Duration `mapstructure:"timeout"`
}
Logging struct {
JSON bool `mapstructure:"json"`
}
Server ServerConfig `mapstructure:"server"`
Shutdown ShutdownConfig `mapstructure:"shutdown"`
Logging LoggingConfig `mapstructure:"logging"`
Telemetry TelemetryConfig `mapstructure:"telemetry"`
}
// ServerConfig holds server-related configuration
type ServerConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
}
// ShutdownConfig holds shutdown-related configuration
type ShutdownConfig struct {
Timeout time.Duration `mapstructure:"timeout"`
}
// LoggingConfig holds logging-related configuration
type LoggingConfig struct {
JSON bool `mapstructure:"json"`
}
// TelemetryConfig holds OpenTelemetry-related configuration
type TelemetryConfig struct {
Enabled bool `mapstructure:"enabled"`
OTLPEndpoint string `mapstructure:"otlp_endpoint"`
ServiceName string `mapstructure:"service_name"`
Insecure bool `mapstructure:"insecure"`
Sampler SamplerConfig `mapstructure:"sampler"`
}
// SamplerConfig holds tracing sampler configuration
type SamplerConfig struct {
Type string `mapstructure:"type"`
Ratio float64 `mapstructure:"ratio"`
}
// LoadConfig loads configuration from file, environment variables, and defaults
@@ -28,13 +53,21 @@ type Config struct {
// To specify a custom config file path, set DLC_CONFIG_FILE environment variable
func LoadConfig() (*Config, error) {
v := viper.New()
// Set default values
v.SetDefault("server.host", "0.0.0.0")
v.SetDefault("server.port", 8080)
v.SetDefault("shutdown.timeout", 30*time.Second)
v.SetDefault("logging.json", false)
// Telemetry defaults
v.SetDefault("telemetry.enabled", false)
v.SetDefault("telemetry.otlp_endpoint", "localhost:4317")
v.SetDefault("telemetry.service_name", "DanceLessonsCoach")
v.SetDefault("telemetry.insecure", true)
v.SetDefault("telemetry.sampler.type", "parentbased_always_on")
v.SetDefault("telemetry.sampler.ratio", 1.0)
// Check for custom config file path via environment variable
if configFile := os.Getenv("DLC_CONFIG_FILE"); configFile != "" {
v.SetConfigFile(configFile)
@@ -45,7 +78,7 @@ func LoadConfig() (*Config, error) {
v.SetConfigType("yaml")
v.AddConfigPath(".")
}
// Read config file if it exists
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
@@ -56,7 +89,7 @@ func LoadConfig() (*Config, error) {
} else {
log.Info().Str("config_file", v.ConfigFileUsed()).Msg("Config file loaded")
}
// Bind environment variables
v.AutomaticEnv()
v.SetEnvPrefix("DLC") // DanceLessonsCoach prefix
@@ -64,25 +97,65 @@ func LoadConfig() (*Config, error) {
v.BindEnv("server.port", "DLC_SERVER_PORT")
v.BindEnv("shutdown.timeout", "DLC_SHUTDOWN_TIMEOUT")
v.BindEnv("logging.json", "DLC_LOGGING_JSON")
// Telemetry environment variables
v.BindEnv("telemetry.enabled", "DLC_TELEMETRY_ENABLED")
v.BindEnv("telemetry.otlp_endpoint", "DLC_TELEMETRY_OTLP_ENDPOINT")
v.BindEnv("telemetry.service_name", "DLC_TELEMETRY_SERVICE_NAME")
v.BindEnv("telemetry.insecure", "DLC_TELEMETRY_INSECURE")
v.BindEnv("telemetry.sampler.type", "DLC_TELEMETRY_SAMPLER_TYPE")
v.BindEnv("telemetry.sampler.ratio", "DLC_TELEMETRY_SAMPLER_RATIO")
// Unmarshal into Config struct
var config Config
if err := v.Unmarshal(&config); err != nil {
log.Error().Err(err).Msg("Failed to unmarshal config")
return nil, fmt.Errorf("config unmarshal error: %w", err)
}
log.Info().
Str("host", config.Server.Host).
Int("port", config.Server.Port).
Dur("shutdown_timeout", config.Shutdown.Timeout).
Bool("logging_json", config.Logging.JSON).
Bool("telemetry_enabled", config.Telemetry.Enabled).
Str("telemetry_service", config.Telemetry.ServiceName).
Msg("Configuration loaded")
return &config, nil
}
// GetServerAddress returns the formatted server address (host:port)
func (c *Config) GetServerAddress() string {
return fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
}
}
// GetTelemetryEnabled returns whether telemetry is enabled
func (c *Config) GetTelemetryEnabled() bool {
return c.Telemetry.Enabled
}
// GetOTLPEndpoint returns the OTLP endpoint for telemetry
func (c *Config) GetOTLPEndpoint() string {
return c.Telemetry.OTLPEndpoint
}
// GetServiceName returns the service name for telemetry
func (c *Config) GetServiceName() string {
return c.Telemetry.ServiceName
}
// GetTelemetryInsecure returns whether to use insecure connection
func (c *Config) GetTelemetryInsecure() bool {
return c.Telemetry.Insecure
}
// GetSamplerType returns the sampler type
func (c *Config) GetSamplerType() string {
return c.Telemetry.Sampler.Type
}
// GetSamplerRatio returns the sampler ratio
func (c *Config) GetSamplerRatio() float64 {
return c.Telemetry.Sampler.Ratio
}