feat: first
This commit is contained in:
100
internal/config/config.go
Normal file
100
internal/config/config.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Config holds all configuration for the application
|
||||
type Config struct {
|
||||
Server ServerConfig
|
||||
Log LogConfig
|
||||
Metrics MetricsConfig
|
||||
}
|
||||
|
||||
// ServerConfig holds server-related configuration
|
||||
type ServerConfig struct {
|
||||
GRPCPort string
|
||||
HTTPPort string
|
||||
}
|
||||
|
||||
// LogConfig holds logging configuration
|
||||
type LogConfig struct {
|
||||
Level string
|
||||
Format string
|
||||
}
|
||||
|
||||
// MetricsConfig holds metrics configuration
|
||||
type MetricsConfig struct {
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
// Load loads configuration from environment variables with defaults
|
||||
func Load() *Config {
|
||||
return &Config{
|
||||
Server: ServerConfig{
|
||||
GRPCPort: getEnv("SERVER_GRPC_PORT", "8080"),
|
||||
HTTPPort: getEnv("SERVER_HTTP_PORT", "8090"),
|
||||
},
|
||||
Log: LogConfig{
|
||||
Level: strings.ToLower(getEnv("LOG_LEVEL", "info")),
|
||||
Format: strings.ToLower(getEnv("LOG_FORMAT", "json")),
|
||||
},
|
||||
Metrics: MetricsConfig{
|
||||
Enabled: getEnvBool("METRICS_ENABLED", true),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates the configuration
|
||||
func (c *Config) Validate() error {
|
||||
if c.Server.GRPCPort == "" {
|
||||
return fmt.Errorf("gRPC port cannot be empty")
|
||||
}
|
||||
if c.Server.HTTPPort == "" {
|
||||
return fmt.Errorf("HTTP port cannot be empty")
|
||||
}
|
||||
if c.Server.GRPCPort == c.Server.HTTPPort {
|
||||
return fmt.Errorf("gRPC and HTTP ports cannot be the same")
|
||||
}
|
||||
|
||||
validLogLevels := map[string]bool{
|
||||
"debug": true,
|
||||
"info": true,
|
||||
"warn": true,
|
||||
"error": true,
|
||||
}
|
||||
if !validLogLevels[c.Log.Level] {
|
||||
return fmt.Errorf("invalid log level: %s", c.Log.Level)
|
||||
}
|
||||
|
||||
validLogFormats := map[string]bool{
|
||||
"json": true,
|
||||
"text": true,
|
||||
}
|
||||
if !validLogFormats[c.Log.Format] {
|
||||
return fmt.Errorf("invalid log format: %s", c.Log.Format)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getEnv gets an environment variable or returns a default value
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// getEnvBool gets a boolean environment variable or returns a default value
|
||||
func getEnvBool(key string, defaultValue bool) bool {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
if parsed, err := strconv.ParseBool(value); err == nil {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
193
internal/config/config_test.go
Normal file
193
internal/config/config_test.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
// Save original env vars
|
||||
originalGRPCPort := os.Getenv("SERVER_GRPC_PORT")
|
||||
originalHTTPPort := os.Getenv("SERVER_HTTP_PORT")
|
||||
originalLogLevel := os.Getenv("LOG_LEVEL")
|
||||
originalLogFormat := os.Getenv("LOG_FORMAT")
|
||||
originalMetricsEnabled := os.Getenv("METRICS_ENABLED")
|
||||
|
||||
// Clean up after test
|
||||
defer func() {
|
||||
os.Setenv("SERVER_GRPC_PORT", originalGRPCPort)
|
||||
os.Setenv("SERVER_HTTP_PORT", originalHTTPPort)
|
||||
os.Setenv("LOG_LEVEL", originalLogLevel)
|
||||
os.Setenv("LOG_FORMAT", originalLogFormat)
|
||||
os.Setenv("METRICS_ENABLED", originalMetricsEnabled)
|
||||
}()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
envVars map[string]string
|
||||
expected Config
|
||||
}{
|
||||
{
|
||||
name: "default values",
|
||||
envVars: map[string]string{},
|
||||
expected: Config{
|
||||
Server: ServerConfig{
|
||||
GRPCPort: "8080",
|
||||
HTTPPort: "8090",
|
||||
},
|
||||
Log: LogConfig{
|
||||
Level: "info",
|
||||
Format: "json",
|
||||
},
|
||||
Metrics: MetricsConfig{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "custom values",
|
||||
envVars: map[string]string{
|
||||
"SERVER_GRPC_PORT": "9090",
|
||||
"SERVER_HTTP_PORT": "9091",
|
||||
"LOG_LEVEL": "debug",
|
||||
"LOG_FORMAT": "text",
|
||||
"METRICS_ENABLED": "false",
|
||||
},
|
||||
expected: Config{
|
||||
Server: ServerConfig{
|
||||
GRPCPort: "9090",
|
||||
HTTPPort: "9091",
|
||||
},
|
||||
Log: LogConfig{
|
||||
Level: "debug",
|
||||
Format: "text",
|
||||
},
|
||||
Metrics: MetricsConfig{
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Clear all env vars first
|
||||
os.Unsetenv("SERVER_GRPC_PORT")
|
||||
os.Unsetenv("SERVER_HTTP_PORT")
|
||||
os.Unsetenv("LOG_LEVEL")
|
||||
os.Unsetenv("LOG_FORMAT")
|
||||
os.Unsetenv("METRICS_ENABLED")
|
||||
|
||||
// Set test env vars
|
||||
for key, value := range tt.envVars {
|
||||
os.Setenv(key, value)
|
||||
}
|
||||
|
||||
cfg := Load()
|
||||
|
||||
if cfg.Server.GRPCPort != tt.expected.Server.GRPCPort {
|
||||
t.Errorf("expected GRPCPort %s, got %s", tt.expected.Server.GRPCPort, cfg.Server.GRPCPort)
|
||||
}
|
||||
if cfg.Server.HTTPPort != tt.expected.Server.HTTPPort {
|
||||
t.Errorf("expected HTTPPort %s, got %s", tt.expected.Server.HTTPPort, cfg.Server.HTTPPort)
|
||||
}
|
||||
if cfg.Log.Level != tt.expected.Log.Level {
|
||||
t.Errorf("expected LogLevel %s, got %s", tt.expected.Log.Level, cfg.Log.Level)
|
||||
}
|
||||
if cfg.Log.Format != tt.expected.Log.Format {
|
||||
t.Errorf("expected LogFormat %s, got %s", tt.expected.Log.Format, cfg.Log.Format)
|
||||
}
|
||||
if cfg.Metrics.Enabled != tt.expected.Metrics.Enabled {
|
||||
t.Errorf("expected MetricsEnabled %t, got %t", tt.expected.Metrics.Enabled, cfg.Metrics.Enabled)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config Config
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid config",
|
||||
config: Config{
|
||||
Server: ServerConfig{
|
||||
GRPCPort: "8080",
|
||||
HTTPPort: "8090",
|
||||
},
|
||||
Log: LogConfig{
|
||||
Level: "info",
|
||||
Format: "json",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty grpc port",
|
||||
config: Config{
|
||||
Server: ServerConfig{
|
||||
GRPCPort: "",
|
||||
HTTPPort: "8090",
|
||||
},
|
||||
Log: LogConfig{
|
||||
Level: "info",
|
||||
Format: "json",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "same ports",
|
||||
config: Config{
|
||||
Server: ServerConfig{
|
||||
GRPCPort: "8080",
|
||||
HTTPPort: "8080",
|
||||
},
|
||||
Log: LogConfig{
|
||||
Level: "info",
|
||||
Format: "json",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid log level",
|
||||
config: Config{
|
||||
Server: ServerConfig{
|
||||
GRPCPort: "8080",
|
||||
HTTPPort: "8090",
|
||||
},
|
||||
Log: LogConfig{
|
||||
Level: "invalid",
|
||||
Format: "json",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid log format",
|
||||
config: Config{
|
||||
Server: ServerConfig{
|
||||
GRPCPort: "8080",
|
||||
HTTPPort: "8090",
|
||||
},
|
||||
Log: LogConfig{
|
||||
Level: "info",
|
||||
Format: "invalid",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.config.Validate()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
112
internal/health/health.go
Normal file
112
internal/health/health.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Response represents a health check response
|
||||
type Response struct {
|
||||
Status string `json:"status"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Checks []Check `json:"checks,omitempty"`
|
||||
}
|
||||
|
||||
// Check represents an individual health check
|
||||
type Check struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Handler handles health check requests
|
||||
type Handler struct {
|
||||
version string
|
||||
checks []HealthChecker
|
||||
}
|
||||
|
||||
// HealthChecker interface for health checks
|
||||
type HealthChecker interface {
|
||||
Name() string
|
||||
Check() error
|
||||
}
|
||||
|
||||
// NewHandler creates a new health check handler
|
||||
func NewHandler(version string) *Handler {
|
||||
return &Handler{
|
||||
version: version,
|
||||
checks: make([]HealthChecker, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// AddCheck adds a health checker
|
||||
func (h *Handler) AddCheck(checker HealthChecker) {
|
||||
h.checks = append(h.checks, checker)
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
response := Response{
|
||||
Status: "ok",
|
||||
Timestamp: time.Now().UTC(),
|
||||
Version: h.version,
|
||||
Checks: make([]Check, 0, len(h.checks)),
|
||||
}
|
||||
|
||||
allHealthy := true
|
||||
for _, checker := range h.checks {
|
||||
check := Check{
|
||||
Name: checker.Name(),
|
||||
Status: "ok",
|
||||
}
|
||||
|
||||
if err := checker.Check(); err != nil {
|
||||
check.Status = "error"
|
||||
check.Error = err.Error()
|
||||
allHealthy = false
|
||||
}
|
||||
|
||||
response.Checks = append(response.Checks, check)
|
||||
}
|
||||
|
||||
if !allHealthy {
|
||||
response.Status = "error"
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// SimpleChecker is a basic health checker
|
||||
type SimpleChecker struct {
|
||||
name string
|
||||
fn func() error
|
||||
}
|
||||
|
||||
// NewSimpleChecker creates a new simple health checker
|
||||
func NewSimpleChecker(name string, fn func() error) *SimpleChecker {
|
||||
return &SimpleChecker{
|
||||
name: name,
|
||||
fn: fn,
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the checker name
|
||||
func (c *SimpleChecker) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
// Check performs the health check
|
||||
func (c *SimpleChecker) Check() error {
|
||||
return c.fn()
|
||||
}
|
||||
115
internal/health/health_test.go
Normal file
115
internal/health/health_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHealthHandler_ServeHTTP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
checkers []HealthChecker
|
||||
expectedStatus int
|
||||
expectedHealth string
|
||||
}{
|
||||
{
|
||||
name: "GET request with no checkers",
|
||||
method: "GET",
|
||||
checkers: []HealthChecker{},
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedHealth: "ok",
|
||||
},
|
||||
{
|
||||
name: "GET request with healthy checker",
|
||||
method: "GET",
|
||||
checkers: []HealthChecker{
|
||||
NewSimpleChecker("test", func() error { return nil }),
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedHealth: "ok",
|
||||
},
|
||||
{
|
||||
name: "GET request with unhealthy checker",
|
||||
method: "GET",
|
||||
checkers: []HealthChecker{
|
||||
NewSimpleChecker("test", func() error { return errors.New("test error") }),
|
||||
},
|
||||
expectedStatus: http.StatusServiceUnavailable,
|
||||
expectedHealth: "error",
|
||||
},
|
||||
{
|
||||
name: "POST request should return 405",
|
||||
method: "POST",
|
||||
checkers: []HealthChecker{},
|
||||
expectedStatus: http.StatusMethodNotAllowed,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
handler := NewHandler("1.0.0")
|
||||
for _, checker := range tt.checkers {
|
||||
handler.AddCheck(checker)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(tt.method, "/health", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != tt.expectedStatus {
|
||||
t.Errorf("expected status %d, got %d", tt.expectedStatus, w.Code)
|
||||
}
|
||||
|
||||
if tt.method == "GET" && tt.expectedStatus != http.StatusMethodNotAllowed {
|
||||
var response Response
|
||||
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
|
||||
if response.Status != tt.expectedHealth {
|
||||
t.Errorf("expected status %s, got %s", tt.expectedHealth, response.Status)
|
||||
}
|
||||
|
||||
if response.Version != "1.0.0" {
|
||||
t.Errorf("expected version 1.0.0, got %s", response.Version)
|
||||
}
|
||||
|
||||
if len(response.Checks) != len(tt.checkers) {
|
||||
t.Errorf("expected %d checks, got %d", len(tt.checkers), len(response.Checks))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleChecker(t *testing.T) {
|
||||
t.Run("healthy checker", func(t *testing.T) {
|
||||
checker := NewSimpleChecker("test", func() error { return nil })
|
||||
|
||||
if checker.Name() != "test" {
|
||||
t.Errorf("expected name 'test', got %s", checker.Name())
|
||||
}
|
||||
|
||||
if err := checker.Check(); err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unhealthy checker", func(t *testing.T) {
|
||||
testErr := errors.New("test error")
|
||||
checker := NewSimpleChecker("test", func() error { return testErr })
|
||||
|
||||
if checker.Name() != "test" {
|
||||
t.Errorf("expected name 'test', got %s", checker.Name())
|
||||
}
|
||||
|
||||
if err := checker.Check(); err != testErr {
|
||||
t.Errorf("expected error %v, got %v", testErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
205
internal/server/server.go
Normal file
205
internal/server/server.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/reflection"
|
||||
|
||||
"go-grpc-gateway-template/internal/config"
|
||||
"go-grpc-gateway-template/internal/health"
|
||||
helloworldpb "go-grpc-gateway-template/proto/helloworld"
|
||||
)
|
||||
|
||||
// Server wraps gRPC and HTTP servers with configuration
|
||||
type Server struct {
|
||||
config *config.Config
|
||||
logger *slog.Logger
|
||||
grpcServer *grpc.Server
|
||||
httpServer *http.Server
|
||||
healthHandler *health.Handler
|
||||
}
|
||||
|
||||
// New creates a new server instance
|
||||
func New(cfg *config.Config) *Server {
|
||||
// Setup logger
|
||||
var handler slog.Handler
|
||||
if cfg.Log.Format == "json" {
|
||||
handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: parseLogLevel(cfg.Log.Level),
|
||||
})
|
||||
} else {
|
||||
handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: parseLogLevel(cfg.Log.Level),
|
||||
})
|
||||
}
|
||||
logger := slog.New(handler)
|
||||
|
||||
// Create health handler
|
||||
healthHandler := health.NewHandler("1.0.0")
|
||||
|
||||
return &Server{
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
healthHandler: healthHandler,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts both gRPC and HTTP servers
|
||||
func (s *Server) Start(ctx context.Context) error {
|
||||
var wg sync.WaitGroup
|
||||
errChan := make(chan error, 2)
|
||||
|
||||
// Start gRPC server
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := s.startGRPCServer(ctx); err != nil {
|
||||
errChan <- fmt.Errorf("gRPC server error: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Start HTTP server
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := s.startHTTPServer(ctx); err != nil && err != http.ErrServerClosed {
|
||||
errChan <- fmt.Errorf("HTTP server error: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for servers to start
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
}()
|
||||
|
||||
// Return first error if any
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
func (s *Server) startGRPCServer(ctx context.Context) error {
|
||||
lis, err := net.Listen("tcp", ":"+s.config.Server.GRPCPort)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen on port %s: %w", s.config.Server.GRPCPort, err)
|
||||
}
|
||||
|
||||
s.grpcServer = grpc.NewServer()
|
||||
|
||||
// Register services
|
||||
greeter := &GreeterService{}
|
||||
helloworldpb.RegisterGreeterServer(s.grpcServer, greeter)
|
||||
|
||||
// Enable reflection for development
|
||||
reflection.Register(s.grpcServer)
|
||||
|
||||
s.logger.Info("Starting gRPC server", "port", s.config.Server.GRPCPort)
|
||||
|
||||
return s.grpcServer.Serve(lis)
|
||||
}
|
||||
|
||||
func (s *Server) startHTTPServer(ctx context.Context) error {
|
||||
// Create gRPC client connection
|
||||
conn, err := grpc.DialContext(
|
||||
ctx,
|
||||
"localhost:"+s.config.Server.GRPCPort,
|
||||
grpc.WithBlock(),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to dial gRPC server: %w", err)
|
||||
}
|
||||
|
||||
// Create gateway mux
|
||||
gwMux := runtime.NewServeMux()
|
||||
|
||||
// Register gRPC gateway handlers
|
||||
err = helloworldpb.RegisterGreeterHandler(ctx, gwMux, conn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register gateway: %w", err)
|
||||
}
|
||||
|
||||
// Create HTTP mux with health endpoint
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", gwMux)
|
||||
mux.Handle("/health", s.healthHandler)
|
||||
|
||||
s.httpServer = &http.Server{
|
||||
Addr: ":" + s.config.Server.HTTPPort,
|
||||
Handler: mux,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
}
|
||||
|
||||
s.logger.Info("Starting HTTP server", "port", s.config.Server.HTTPPort)
|
||||
|
||||
return s.httpServer.ListenAndServe()
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the servers
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
s.logger.Info("Shutting down servers...")
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Shutdown HTTP server
|
||||
if s.httpServer != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := s.httpServer.Shutdown(ctx); err != nil {
|
||||
s.logger.Error("HTTP server shutdown error", "error", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Shutdown gRPC server
|
||||
if s.grpcServer != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
s.grpcServer.GracefulStop()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
s.logger.Info("Servers stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GreeterService implements the helloworld.Greeter service
|
||||
type GreeterService struct {
|
||||
helloworldpb.UnimplementedGreeterServer
|
||||
}
|
||||
|
||||
// SayHello implements the SayHello RPC method
|
||||
func (g *GreeterService) SayHello(ctx context.Context, req *helloworldpb.HelloRequest) (*helloworldpb.HelloReply, error) {
|
||||
return &helloworldpb.HelloReply{
|
||||
Message: "Hello " + req.GetName(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseLogLevel(level string) slog.Level {
|
||||
switch level {
|
||||
case "debug":
|
||||
return slog.LevelDebug
|
||||
case "info":
|
||||
return slog.LevelInfo
|
||||
case "warn":
|
||||
return slog.LevelWarn
|
||||
case "error":
|
||||
return slog.LevelError
|
||||
default:
|
||||
return slog.LevelInfo
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user