feat(service): implement graceful shutdown for HTTP server

Add a context with timeout to handle graceful shutdown of the HTTP 
server. Update error handling during the server's close to include 
context-aware shutdown. Ensure that the server properly logs only 
non-closed errors when listening.
This commit is contained in:
2025-04-12 10:43:40 +02:00
parent 8e02bfb0a2
commit aaa111dd20
5 changed files with 124 additions and 22 deletions
+52
View File
@@ -0,0 +1,52 @@
package logging
import (
"context"
"log/slog"
"os"
)
type Logger interface {
Info(msg string, args ...any)
Warn(msg string, args ...any)
Error(msg string, args ...any)
}
var defaultLogger *slog.Logger
type contextKey string
const loggerKey = contextKey("logger")
func SetupLogger(logLevel, serviceName, buildVersion string) *slog.Logger {
var leveler slog.LevelVar
err := leveler.UnmarshalText([]byte(logLevel))
defaultLogger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: false,
Level: leveler.Level(),
ReplaceAttr: nil,
})).With("service", serviceName).With("version", buildVersion)
if err != nil {
defaultLogger.With("err", err).Error("Failed to parse log level")
os.Exit(1)
}
slog.SetDefault(defaultLogger)
return defaultLogger
}
// ContextWithLogger returns a new Context with the logger attached
func ContextWithLogger(ctx context.Context, logger *slog.Logger) context.Context {
return context.WithValue(ctx, loggerKey, logger)
}
// LoggerFromContext returns a logger from the passed context or the default logger
func LoggerFromContext(ctx context.Context) *slog.Logger {
logger := ctx.Value(loggerKey)
if l, ok := logger.(*slog.Logger); ok {
return l
}
return defaultLogger
}
+48
View File
@@ -0,0 +1,48 @@
package logging
import (
"bytes"
"log/slog"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func NewMockLogger() *MockLogger {
logged := &bytes.Buffer{}
return &MockLogger{
logged: logged,
logger: slog.New(slog.NewTextHandler(logged, &slog.HandlerOptions{
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == "time" {
return slog.Attr{}
}
return a
},
})),
}
}
type MockLogger struct {
logger *slog.Logger
logged *bytes.Buffer
}
func (m *MockLogger) Logger() *slog.Logger {
return m.logger
}
func (m *MockLogger) Check(t testing.TB, wantLogged []string) {
var gotLogged []string
if m.logged.String() != "" {
gotLogged = strings.Split(m.logged.String(), "\n")
gotLogged = gotLogged[:len(gotLogged)-1]
}
if len(wantLogged) == 0 {
assert.Empty(t, gotLogged)
return
}
assert.Equal(t, wantLogged, gotLogged)
}