Skip to content

Commit

Permalink
Start preparing application for taking yaml config
Browse files Browse the repository at this point in the history
  • Loading branch information
kuskoman committed Dec 16, 2023
1 parent c9a9ac7 commit e05a8c6
Show file tree
Hide file tree
Showing 13 changed files with 433 additions and 65 deletions.
23 changes: 18 additions & 5 deletions cmd/exporter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"log"
"log/slog"
"os"
"strconv"

"github.com/joho/godotenv"
"github.com/kuskoman/logstash-exporter/collectors"
Expand Down Expand Up @@ -34,16 +35,28 @@ func main() {
}
slog.SetDefault(logger)

port, host := config.Port, config.Host
logstashUrl := config.LogstashUrl
exporterConfig, err := config.GetConfig(config.ExporterConfigLocation)
if err != nil {
slog.Error("failed to get exporter config", "err", err)
os.Exit(1)
}

host := exporterConfig.Server.Host
port := strconv.Itoa(exporterConfig.Server.Port)

slog.Debug("application starting... ")
versionInfo := config.GetVersionInfo()
slog.Info(versionInfo.String())

collectorManager := collectors.NewCollectorManager(logstashUrl)
appServer := server.NewAppServer(host, port)
prometheus.MustRegister(collectorManager)
for _, logstashServerConfig := range exporterConfig.Logstash.Servers {
logstashUrl := logstashServerConfig.URL
slog.Info("booting collector manager for", "logstashUrl", logstashUrl)

collectorManager := collectors.NewCollectorManager(logstashUrl)
prometheus.MustRegister(collectorManager)
}

appServer := server.NewAppServer(host, port, exporterConfig)

slog.Info("starting server on", "host", host, "port", port)
if err := appServer.ListenAndServe(); err != nil {
Expand Down
115 changes: 115 additions & 0 deletions config/exporter_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package config

import (
"log/slog"
"os"

"gopkg.in/yaml.v2"
)

const (
defaultConfigLocation = "config.yml"
defaultPort = 9198
defaultLogLevel = "info"
defaultLogstashURL = "http://localhost:9600"
)

var (
ExporterConfigLocation = getEnvWithDefault("EXPORTER_CONFIG_LOCATION", defaultConfigLocation)
)

// LogstashServer represents individual Logstash server configuration
type LogstashServer struct {
URL string `yaml:"url"`
}

// LogstashConfig holds the configuration for all Logstash servers
type LogstashConfig struct {
Servers []LogstashServer `yaml:"servers"`
}

// ServerConfig represents the server configuration
type ServerConfig struct {
// Host is the host the exporter will listen on.
// Defaults to an empty string, which will listen on all interfaces
// Can be overridden by setting the HOST environment variable
// For windows, use "localhost", because an empty string will not work
// with the default windows firewall configuration.
// Alternatively you can change the firewall configuration to allow
// connections to the port from all interfaces.
Host string `yaml:"host"`
Port int `yaml:"port"`
}

// LoggingConfig represents the logging configuration
type LoggingConfig struct {
Level string `yaml:"level"`
}

// Config represents the overall configuration loaded from the YAML file
type Config struct {
Logstash LogstashConfig `yaml:"logstash"`
Server ServerConfig `yaml:"server"`
Logging LoggingConfig `yaml:"logging"`
}

// loadConfig loads the configuration from the YAML file.
func loadConfig(location string) (*Config, error) {
data, err := os.ReadFile(location)
if err != nil {
return nil, err
}

var config Config
err = yaml.Unmarshal(data, &config)
if err != nil {
return nil, err
}

return &config, nil
}

// mergeWithDefault merges the loaded configuration with the default configuration values.
func mergeWithDefault(config *Config) *Config {
if config == nil {
config = &Config{}
}

if config.Server.Port == 0 {
slog.Debug("using default port", "port", defaultPort)
config.Server.Port = defaultPort
}

if config.Logging.Level == "" {
slog.Debug("using default log level", "level", defaultLogLevel)
config.Logging.Level = defaultLogLevel
}

if len(config.Logstash.Servers) == 0 {
slog.Debug("using default logstash server", "url", defaultLogstashURL)
config.Logstash.Servers = append(config.Logstash.Servers, LogstashServer{
URL: defaultLogstashURL,
})
}

return config
}

// GetConfig combines loadConfig and mergeWithDefault to get the final configuration.
func GetConfig(location string) (*Config, error) {
config, err := loadConfig(location)
if err != nil {
return nil, err
}

mergedConfig := mergeWithDefault(config)
return mergedConfig, nil
}

func (cfg *Config) GetLogstashUrls() []string {
urls := make([]string, len(cfg.Logstash.Servers))
for i, server := range cfg.Logstash.Servers {
urls[i] = server.URL
}
return urls
}
173 changes: 173 additions & 0 deletions config/exporter_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package config

import (
"reflect"
"testing"
)

func TestLoadConfig(t *testing.T) {
t.Parallel()
t.Run("loads valid config", func(t *testing.T) {
t.Parallel()

location := "../fixtures/valid_config.yml"
config, err := loadConfig(location)

if err != nil {
t.Fatalf("got an error %v", err)
}
if config == nil {
t.Fatal("expected config to be non-nil")
}
if config.Logstash.Servers[0].URL != "http://localhost:9601" {
t.Errorf("expected URL to be %v, got %v", "http://localhost:9601", config.Logstash.Servers[0].URL)
}
})

t.Run("returns error for non-existent file", func(t *testing.T) {
t.Parallel()

location := "../fixtures/non_existent.yml"
config, err := loadConfig(location)

if err == nil {
t.Fatal("expected error, got none")
}
if config != nil {
t.Fatal("expected config to be nil")
}
})

t.Run("returns error for invalid config", func(t *testing.T) {
t.Parallel()

location := "../fixtures/invalid_config.toml"
config, err := loadConfig(location)

if err == nil {
t.Fatal("expected error, got none")
}

if config != nil {
t.Fatal("expected config to be nil")
}
})
}

func TestMergeWithDefault(t *testing.T) {
t.Parallel()

t.Run("merge with empty config", func(t *testing.T) {
t.Parallel()

config := &Config{}
mergedConfig := mergeWithDefault(config)

if mergedConfig.Server.Port != defaultPort {
t.Errorf("expected port to be %v, got %v", defaultPort, mergedConfig.Server.Port)
}
if mergedConfig.Logging.Level != defaultLogLevel {
t.Errorf("expected level to be %v, got %v", defaultLogLevel, mergedConfig.Logging.Level)
}
if mergedConfig.Logstash.Servers[0].URL != defaultLogstashURL {
t.Errorf("expected URL to be %v, got %v", defaultLogstashURL, mergedConfig.Logstash.Servers[0].URL)
}
})

t.Run("merge with nil config", func(t *testing.T) {
t.Parallel()

mergedConfig := mergeWithDefault(nil)

if mergedConfig.Server.Port != defaultPort {
t.Errorf("expected port to be %v, got %v", defaultPort, mergedConfig.Server.Port)
}
if mergedConfig.Logging.Level != defaultLogLevel {
t.Errorf("expected level to be %v, got %v", defaultLogLevel, mergedConfig.Logging.Level)
}
if mergedConfig.Logstash.Servers[0].URL != defaultLogstashURL {
t.Errorf("expected URL to be %v, got %v", defaultLogstashURL, mergedConfig.Logstash.Servers[0].URL)
}
})

t.Run("merge with non-empty config", func(t *testing.T) {
t.Parallel()

config := &Config{
Server: ServerConfig{
Port: 1234,
},
Logging: LoggingConfig{
Level: "debug",
},
Logstash: LogstashConfig{
Servers: []LogstashServer{
{URL: "http://localhost:9601"},
{URL: "http://localhost:9602"},
},
},
}

mergedConfig := mergeWithDefault(config)

if mergedConfig.Server.Port != 1234 {
t.Errorf("expected port to be %v, got %v", 1234, mergedConfig.Server.Port)
}

if mergedConfig.Logging.Level != "debug" {
t.Errorf("expected level to be %v, got %v", "debug", mergedConfig.Logging.Level)
}

if mergedConfig.Logstash.Servers[0].URL != "http://localhost:9601" {
t.Errorf("expected URL to be %v, got %v", "http://localhost:9601", mergedConfig.Logstash.Servers[0].URL)
}

if mergedConfig.Logstash.Servers[1].URL != "http://localhost:9602" {
t.Errorf("expected URL to be %v, got %v", "http://localhost:9602", mergedConfig.Logstash.Servers[1].URL)
}
})
}

func TestGetConfig(t *testing.T) {
t.Run("returns valid config", func(t *testing.T) {

location := "../fixtures/valid_config.yml"
config, err := GetConfig(location)

if err != nil {
t.Fatalf("got an error %v", err)
}
if config == nil {
t.Fatal("expected config to be non-nil")
}
})

t.Run("returns error for invalid config", func(t *testing.T) {
location := "../fixtures/invalid_config.yml"
config, err := GetConfig(location)

if err == nil {
t.Fatal("expected error, got none")
}
if config != nil {
t.Fatal("expected config to be nil")
}
})
}

func TestGetLogstashUrls(t *testing.T) {
t.Run("gets Logstash URLs correctly", func(t *testing.T) {
config := &Config{
Logstash: LogstashConfig{
Servers: []LogstashServer{{URL: "http://localhost:9601"}},
},
}

urls := config.GetLogstashUrls()
expectedUrls := []string{"http://localhost:9601"}

if !reflect.DeepEqual(urls, expectedUrls) {
t.Errorf("expected urls to be %v, got %v", expectedUrls, urls)
}
})
}
8 changes: 0 additions & 8 deletions config/logstash_config.go

This file was deleted.

17 changes: 0 additions & 17 deletions config/server_config.go

This file was deleted.

2 changes: 2 additions & 0 deletions fixtures/invalid_config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[config]
theConfigShouldBe = 'yaml'
8 changes: 8 additions & 0 deletions fixtures/valid_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
logstash:
servers:
- url: "http://localhost:9601"
server:
host: "127.0.0.1"
port: 9200
logging:
level: "debug"
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ toolchain go1.21.4
require (
github.com/joho/godotenv v1.5.1
github.com/prometheus/client_golang v1.17.0
gopkg.in/yaml.v2 v2.4.0
)

require (
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
Loading

0 comments on commit e05a8c6

Please sign in to comment.