feat(observabillity): #115 integrate otel for metrics and traces
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m51s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 5m46s

This commit was merged in pull request #152.
This commit is contained in:
2025-06-07 12:18:41 +02:00
parent b336b65532
commit 3e7251ef9d
32 changed files with 480 additions and 314 deletions

View File

@@ -19,56 +19,67 @@ import (
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func Run(ctx context.Context, database *sqlx.DB, migrationsPrefix string, env func(string) string) error {
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
defer cancel()
log.Info("Starting server...")
log.L.Info("Starting server...")
// init server settings
serverSettings := types.NewSettingsFromEnv(env)
serverSettings, err := types.NewSettingsFromEnv(env)
if err != nil {
return err
}
// init db
err := db.RunMigrations(database, migrationsPrefix)
err = db.RunMigrations(database, migrationsPrefix)
if err != nil {
return fmt.Errorf("could not run migrations: %w", err)
}
// init servers
var prometheusServer *http.Server
if serverSettings.PrometheusEnabled {
prometheusServer := &http.Server{
Addr: ":8081",
Handler: promhttp.Handler(),
ReadHeaderTimeout: 10 * time.Second,
if serverSettings.OtelEnabled {
// use context.Background(), otherwise the shutdown can't be called, as the context is already cancelled
otelShutdown, err := setupOTelSDK(context.Background())
if err != nil {
return fmt.Errorf("could not setup OpenTelemetry SDK: %w", err)
}
go startServer(prometheusServer)
defer func() {
// User context.Background(), as the main context is already cancelled
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
err = otelShutdown(ctx)
if err != nil {
log.L.Error("error shutting down OpenTelemetry SDK", "err", err)
}
cancel()
}()
log.InitOtelLogger()
}
// init server
httpServer := &http.Server{
Addr: ":" + serverSettings.Port,
Handler: createHandler(database, serverSettings),
ReadHeaderTimeout: 10 * time.Second,
ReadHeaderTimeout: 2 * time.Second,
}
go startServer(httpServer)
// graceful shutdown
var wg sync.WaitGroup
wg.Add(2)
wg.Add(1)
go shutdownServer(httpServer, ctx, &wg)
go shutdownServer(prometheusServer, ctx, &wg)
wg.Wait()
return nil
}
func startServer(s *http.Server) {
log.Info("Starting server on %q", s.Addr)
log.L.Info("Starting server", "addr", s.Addr)
if err := s.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Error("error listening and serving: %v", err)
log.L.Error("error listening and serving", "err", err)
}
}
@@ -83,9 +94,9 @@ func shutdownServer(s *http.Server, ctx context.Context, wg *sync.WaitGroup) {
shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second)
defer cancel()
if err := s.Shutdown(shutdownCtx); err != nil {
log.Error("error shutting down http server: %v", err)
log.L.Error("error shutting down http server", "err", err)
} else {
log.Info("Gracefully stopped http server on %v", s.Addr)
log.L.Info("Gracefully stopped http server", "addr", s.Addr)
}
}
@@ -122,7 +133,7 @@ func createHandler(d *sqlx.DB, serverSettings *types.Settings) http.Handler {
// Serve static files (CSS, JS and images)
router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
return middleware.Wrapper(
wrapper := middleware.Wrapper(
router,
middleware.GenerateRecurringTransactions(transactionRecurringService),
middleware.SecurityHeaders(serverSettings),
@@ -132,4 +143,8 @@ func createHandler(d *sqlx.DB, serverSettings *types.Settings) http.Handler {
middleware.Gzip,
middleware.Log,
)
wrapper = otelhttp.NewHandler(wrapper, "http.request")
return wrapper
}