forked from templates/template-go-backend
Fix logging and database configuration
This commit is contained in:
@@ -15,23 +15,26 @@ import (
|
||||
func LoadConfig(logger *slog.Logger) (*Config, error) {
|
||||
_ = godotenv.Load() // необязательно фейлиться, если файла нет
|
||||
|
||||
cfg := &Config{
|
||||
Timezone: GetEnvAs("TIMEZONE", "UTC", ParseString),
|
||||
ServiceURL: GetEnvAs("SERVICE_URL", "http://localhost:8080", ParseString),
|
||||
LoggingConfig: LoggingConfig{
|
||||
Instance: logger,
|
||||
Level: GetEnvAs("LOG_LEVEL", "info", ParseString),
|
||||
},
|
||||
DatabaseConfig: FillDatabaseConfig(),
|
||||
}
|
||||
cfg := &Config{}
|
||||
|
||||
printConfig(cfg)
|
||||
cfg.Timezone = GetEnvAs("TIMEZONE", "UTC", ParseString)
|
||||
cfg.ServiceURL = GetEnvAs("SERVICE_URL", "http://localhost:8080", ParseString)
|
||||
|
||||
cfg.LoggingConfig.Instance = logger
|
||||
cfg.LoggingConfig.Level = GetEnvAs("LOG_LEVEL", "info", ParseString)
|
||||
cfg.LoggingConfig.ShowCfgDump = GetEnvAs("LOG_SHOW_DUMP", false, ParseBool)
|
||||
|
||||
cfg.DatabaseConfig = FillDatabaseConfig()
|
||||
|
||||
if cfg.LoggingConfig.ShowCfgDump {
|
||||
cfg.Print()
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// PrintConfig выводит конфигурацию (или любой другой struct) в виде таблички "KEY - VALUE".
|
||||
// Функция использует рефлексию для перебора полей структуры.
|
||||
func printConfig(cfg any) {
|
||||
func (c *Config) Print() {
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "Loaded configuration:")
|
||||
@@ -39,7 +42,7 @@ func printConfig(cfg any) {
|
||||
fmt.Fprintln(w, "----\t-----")
|
||||
|
||||
// Получаем reflect.Value объекта.
|
||||
v := reflect.ValueOf(cfg)
|
||||
v := reflect.ValueOf(c)
|
||||
// Если передан указатель, получаем значение, на которое он указывает.
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
|
||||
@@ -2,10 +2,12 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DatabaseConfig struct {
|
||||
URL string
|
||||
Kind string
|
||||
Host string
|
||||
Port string
|
||||
@@ -17,27 +19,95 @@ type DatabaseConfig struct {
|
||||
}
|
||||
|
||||
func FillDatabaseConfig() DatabaseConfig {
|
||||
databaseURL := GetEnvAs("DATABASE_URL", "", ParseString)
|
||||
|
||||
if databaseURL == "" {
|
||||
return DatabaseConfig{}
|
||||
}
|
||||
|
||||
u, err := url.Parse(databaseURL)
|
||||
if err != nil {
|
||||
return DatabaseConfig{}
|
||||
}
|
||||
|
||||
kind := "postgres"
|
||||
if u.Scheme == "postgresql" {
|
||||
kind = "postgres"
|
||||
} else if u.Scheme != "" {
|
||||
kind = u.Scheme
|
||||
}
|
||||
|
||||
dbName := ""
|
||||
if len(u.Path) > 1 {
|
||||
dbName = u.Path[1:]
|
||||
}
|
||||
|
||||
return DatabaseConfig{
|
||||
Kind: GetEnvAs("DATABASE_KIND", "postgres", ParseString),
|
||||
Host: GetEnvAs("DATABASE_HOST", "localhost", ParseString),
|
||||
Port: GetEnvAs("DATABASE_PORT", "5432", ParseString),
|
||||
User: GetEnvAs("DATABASE_USER", "chudovo", ParseString),
|
||||
Password: GetEnvAs("DATABASE_PASS", "top_secret", ParseString),
|
||||
Name: GetEnvAs("DATABASE_NAME", "chudovo", ParseString),
|
||||
UseTLS: GetEnvAs("DATABASE_USETLS", false, ParseBool),
|
||||
URL: databaseURL,
|
||||
Kind: kind,
|
||||
Host: u.Hostname(),
|
||||
Port: u.Port(),
|
||||
User: u.User.Username(),
|
||||
Password: func() string {
|
||||
password, _ := u.User.Password()
|
||||
return password
|
||||
}(),
|
||||
Name: dbName,
|
||||
UseTLS: GetEnvAs("DATABASE_USETLS", false, ParseBool),
|
||||
Timeout: GetEnvAs("DATABASE_TIMEOUT", 30*time.Second, ParseDuration),
|
||||
}
|
||||
}
|
||||
|
||||
func GetDatabaseDSN(cfg *DatabaseConfig) string {
|
||||
// GetDatabaseURLForLogging возвращает URL базы данных для логирования, скрывая пароль.
|
||||
func GetDatabaseURLForLogging(cfg *DatabaseConfig) (string, error) {
|
||||
if cfg.URL == "" {
|
||||
return "", fmt.Errorf("parameter DATABASE_URL is empty")
|
||||
}
|
||||
|
||||
u, err := url.Parse(cfg.URL)
|
||||
if err == nil {
|
||||
if u.User != nil {
|
||||
username := u.User.Username()
|
||||
s := u.Scheme + "://" + username + ":***@" + u.Host
|
||||
if u.Path != "" {
|
||||
s += u.Path
|
||||
}
|
||||
if u.RawQuery != "" {
|
||||
s += "?" + u.RawQuery
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
}
|
||||
return u.String(), err
|
||||
}
|
||||
|
||||
func GetDatabaseDSN(cfg *DatabaseConfig) (string, error) {
|
||||
if cfg.URL == "" {
|
||||
return "", fmt.Errorf("parameter DATABASE_URL is empty")
|
||||
}
|
||||
|
||||
u, err := url.Parse(cfg.URL)
|
||||
if err == nil {
|
||||
query := u.Query()
|
||||
if !cfg.UseTLS {
|
||||
query.Set("sslmode", "disable")
|
||||
} else if query.Get("sslmode") == "" {
|
||||
query.Set("sslmode", "require")
|
||||
}
|
||||
u.RawQuery = query.Encode()
|
||||
return u.String(), err
|
||||
}
|
||||
|
||||
dsn := fmt.Sprintf(
|
||||
"host=%s port=%s user=%s password=%s dbname=%s",
|
||||
cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.Name,
|
||||
)
|
||||
|
||||
// Если TLS отключен, добавляем параметр sslmode=disable
|
||||
if !cfg.UseTLS {
|
||||
dsn += " sslmode=disable"
|
||||
} else {
|
||||
dsn += " sslmode=require"
|
||||
}
|
||||
|
||||
return dsn
|
||||
return dsn, nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import "log/slog"
|
||||
type LoggingConfig struct {
|
||||
Instance *slog.Logger
|
||||
Level string
|
||||
ShowCanDump bool
|
||||
ShowCfgDump bool
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
||||
28
src/internal/logging/logging.go
Normal file
28
src/internal/logging/logging.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// New creates a slog logger with the provided level string (e.g., "debug", "info").
|
||||
func New(level string) *slog.Logger {
|
||||
return slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: ParseLevel(level)}))
|
||||
}
|
||||
|
||||
// ParseLevel converts a string level to slog.Level, defaults to info on unknown.
|
||||
func ParseLevel(lvl string) slog.Level {
|
||||
switch strings.ToLower(lvl) {
|
||||
case "debug":
|
||||
return slog.LevelDebug
|
||||
case "warn", "warning":
|
||||
return slog.LevelWarn
|
||||
case "error":
|
||||
return slog.LevelError
|
||||
case "info", "":
|
||||
return slog.LevelInfo
|
||||
default:
|
||||
return slog.LevelInfo
|
||||
}
|
||||
}
|
||||
27
src/main.go
27
src/main.go
@@ -11,6 +11,7 @@ import (
|
||||
"backend/ent"
|
||||
"backend/src/internal/config"
|
||||
"backend/src/internal/gateway"
|
||||
"backend/src/internal/logging"
|
||||
"backend/src/logic"
|
||||
|
||||
_ "github.com/lib/pq" // побочный импорт драйвера PostgreSQL
|
||||
@@ -24,8 +25,8 @@ const (
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
logger := slog.Default()
|
||||
// slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||
logger := logging.New("info")
|
||||
slog.SetDefault(logger)
|
||||
|
||||
logger.Info(fmt.Sprintf("Starting %s version %s\n", AppName, AppVersion))
|
||||
|
||||
@@ -35,12 +36,32 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
// adjust logger according to LOG_LEVEL
|
||||
level := logging.ParseLevel(cfg.LoggingConfig.Level)
|
||||
if level != slog.LevelInfo {
|
||||
logger.Info("Adjusting log level from env", "level", level.String())
|
||||
}
|
||||
logger = logging.New(cfg.LoggingConfig.Level)
|
||||
slog.SetDefault(logger)
|
||||
cfg.LoggingConfig.Instance = logger
|
||||
|
||||
// создаем контекст с отменой для управления жизненным циклом сервиса.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// подключаемся к базе данных
|
||||
dsn := config.GetDatabaseDSN(&cfg.DatabaseConfig)
|
||||
dbURL, err := config.GetDatabaseURLForLogging(&cfg.DatabaseConfig)
|
||||
if err != nil {
|
||||
logger.Error("Failed getting database URL for logging", "error", err)
|
||||
return
|
||||
}
|
||||
logger.Info("Connecting to database...", "url", dbURL)
|
||||
|
||||
dsn, err := config.GetDatabaseDSN(&cfg.DatabaseConfig)
|
||||
if err != nil {
|
||||
logger.Error("Failed getting database DSN", "error", err)
|
||||
return
|
||||
}
|
||||
db, err := ent.Open(cfg.DatabaseConfig.Kind, dsn)
|
||||
if err != nil {
|
||||
logger.Error("Failed opening connection to postgres", "error", err)
|
||||
|
||||
Reference in New Issue
Block a user