Skip to content

Commit

Permalink
Add environment variable to configure http timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
Jakub Surdej committed Jan 11, 2024
1 parent a8f242b commit 03f1f4d
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 20 deletions.
11 changes: 9 additions & 2 deletions cmd/exporter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,15 @@ func main() {
versionInfo := config.GetVersionInfo()
slog.Info(versionInfo.String())

collectorManager := collectors.NewCollectorManager(logstashUrl)
appServer := server.NewAppServer(host, port)
httpTimeout, err := config.GetHttpTimeout()
if err != nil {
slog.Error("failed to get http timeout", "err", err)
os.Exit(1)
}
slog.Debug("http timeout", "timeout", httpTimeout)

collectorManager := collectors.NewCollectorManager(logstashUrl, httpTimeout)
appServer := server.NewAppServer(host, port, httpTimeout)
prometheus.MustRegister(collectorManager)

slog.Info("starting server on", "host", host, "port", port)
Expand Down
7 changes: 4 additions & 3 deletions collectors/collector_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,18 @@ type Collector interface {
type CollectorManager struct {
collectors map[string]Collector
scrapeDurations *prometheus.SummaryVec
timeout time.Duration
}

func NewCollectorManager(endpoint string) *CollectorManager {
func NewCollectorManager(endpoint string, timeout time.Duration) *CollectorManager {
client := logstashclient.NewClient(endpoint)

collectors := getCollectors(client)

scrapeDurations := getScrapeDurationsCollector()
prometheus.MustRegister(version.NewCollector("logstash_exporter"))

return &CollectorManager{collectors: collectors, scrapeDurations: scrapeDurations}
return &CollectorManager{collectors: collectors, scrapeDurations: scrapeDurations, timeout: timeout}
}

func getCollectors(client logstashclient.Client) map[string]Collector {
Expand All @@ -45,7 +46,7 @@ func getCollectors(client logstashclient.Client) map[string]Collector {
// Collect executes all collectors and sends the collected metrics to the provided channel.
// It also sends the duration of the collection to the scrapeDurations collector.
func (manager *CollectorManager) Collect(ch chan<- prometheus.Metric) {
ctx, cancel := context.WithTimeout(context.Background(), config.HttpTimeout)
ctx, cancel := context.WithTimeout(context.Background(), manager.timeout)

defer cancel()

Expand Down
5 changes: 4 additions & 1 deletion collectors/collector_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import (
"errors"
"sync"
"testing"
"time"

"github.com/prometheus/client_golang/prometheus"
)

const httpTimeout = 2 * time.Second

func TestNewCollectorManager(t *testing.T) {
mockEndpoint := "http://localhost:9600"
cm := NewCollectorManager(mockEndpoint)
cm := NewCollectorManager(mockEndpoint, httpTimeout)

if cm == nil {
t.Error("Expected collector manager to be initialized")
Expand Down
25 changes: 22 additions & 3 deletions config/http_config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
package config

import "time"
import (
"os"
"time"
)

// HttpTimeout is the timeout for http requests utilized by multiple contexts
const HttpTimeout = time.Second * 2
const (
defaultHttpTimeout = time.Second * 2
httpTimeoutEnvVar = "HTTP_TIMEOUT"
)

func GetHttpTimeout() (time.Duration, error) {
userDefinedTimeout := os.Getenv(httpTimeoutEnvVar)
if userDefinedTimeout == "" {
return defaultHttpTimeout, nil
}

timeout, err := time.ParseDuration(userDefinedTimeout)
if err != nil {
return 0, err
}

return timeout, nil
}
46 changes: 46 additions & 0 deletions config/http_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package config

import (
"os"
"testing"
"time"
)

func TestGetHttpTimeout(t *testing.T) {
t.Run("DefaultTimeout", func(t *testing.T) {
t.Parallel()
os.Unsetenv(httpTimeoutEnvVar)
timeout, err := GetHttpTimeout()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if timeout != defaultHttpTimeout {
t.Errorf("Expected default timeout of %v, got %v", defaultHttpTimeout, timeout)
}
})

t.Run("CustomTimeout", func(t *testing.T) {
t.Parallel()
expectedTimeout := "5s"
os.Setenv(httpTimeoutEnvVar, expectedTimeout)
defer os.Unsetenv(httpTimeoutEnvVar)
timeout, err := GetHttpTimeout()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
parsedTimeout, _ := time.ParseDuration(expectedTimeout)
if timeout != parsedTimeout {
t.Errorf("Expected timeout of %v, got %v", parsedTimeout, timeout)
}
})

t.Run("InvalidTimeout", func(t *testing.T) {
t.Parallel()
os.Setenv(httpTimeoutEnvVar, "invalid")
defer os.Unsetenv(httpTimeoutEnvVar)
_, err := GetHttpTimeout()
if err == nil {
t.Error("Expected an error for invalid timeout, but got nil")
}
})
}
7 changes: 3 additions & 4 deletions server/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ package server
import (
"context"
"net/http"

"github.com/kuskoman/logstash-exporter/config"
"time"
)

func getHealthCheck(logstashURL string) func(http.ResponseWriter, *http.Request) {
func getHealthCheck(logstashURL string, timeout time.Duration) func(http.ResponseWriter, *http.Request) {
client := &http.Client{}
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), config.HttpTimeout)
ctx, cancel := context.WithTimeout(r.Context(), timeout)
defer cancel()

req, err := http.NewRequestWithContext(ctx, http.MethodGet, logstashURL, nil)
Expand Down
6 changes: 3 additions & 3 deletions server/healthcheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestHealthCheck(t *testing.T) {
}))
defer mockServer.Close()

handler := getHealthCheck(mockServer.URL)
handler := getHealthCheck(mockServer.URL, defaultHttpTimeout)
req, err := http.NewRequest(http.MethodGet, "/", nil)
if err != nil {
t.Fatalf("Error creating request: %v", err)
Expand All @@ -40,7 +40,7 @@ func TestHealthCheck(t *testing.T) {
})

t.Run("no response", func(t *testing.T) {
handler := getHealthCheck("http://localhost:12345")
handler := getHealthCheck("http://localhost:12345", defaultHttpTimeout)
req, err := http.NewRequest(http.MethodGet, "/", nil)
if err != nil {
t.Fatalf("Error creating request: %v", err)
Expand All @@ -55,7 +55,7 @@ func TestHealthCheck(t *testing.T) {
})

t.Run("invalid url", func(t *testing.T) {
handler := getHealthCheck("http://localhost:96010:invalidurl")
handler := getHealthCheck("http://localhost:96010:invalidurl", defaultHttpTimeout)
req, err := http.NewRequest(http.MethodGet, "/", nil)
if err != nil {
t.Fatalf("Error creating request: %v", err)
Expand Down
5 changes: 3 additions & 2 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package server
import (
"fmt"
"net/http"
"time"

"github.com/kuskoman/logstash-exporter/config"
"github.com/prometheus/client_golang/prometheus/promhttp"
Expand All @@ -12,13 +13,13 @@ import (
// and registers the prometheus handler and the healthcheck handler
// to the server's mux. The prometheus handler is managed under the
// hood by the prometheus client library.
func NewAppServer(host, port string) *http.Server {
func NewAppServer(host, port string, httpTimeout time.Duration) *http.Server {
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.Handler())
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/metrics", http.StatusMovedPermanently)
})
mux.HandleFunc("/healthcheck", getHealthCheck(config.LogstashUrl))
mux.HandleFunc("/healthcheck", getHealthCheck(config.LogstashUrl, httpTimeout))
mux.HandleFunc("/version", getVersionInfoHandler(config.GetVersionInfo()))

listenUrl := fmt.Sprintf("%s:%s", host, port)
Expand Down
7 changes: 5 additions & 2 deletions server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import (
"net/http"
"net/http/httptest"
"testing"
"time"
)

const defaultHttpTimeout = 2 * time.Second

func TestNewAppServer(t *testing.T) {
t.Run("Test handling of /metrics endpoint", func(t *testing.T) {
server := NewAppServer("", "8080")
server := NewAppServer("", "8080", defaultHttpTimeout)
req, err := http.NewRequest("GET", "/metrics", nil)
if err != nil {
t.Fatal(err)
Expand All @@ -21,7 +24,7 @@ func TestNewAppServer(t *testing.T) {
})

t.Run("Test handling of / endpoint", func(t *testing.T) {
server := NewAppServer("", "8080")
server := NewAppServer("", "8080", defaultHttpTimeout)
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
Expand Down

0 comments on commit 03f1f4d

Please sign in to comment.