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:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user