Fix logging and database configuration

This commit is contained in:
2026-02-07 13:03:31 +03:00
parent a2d6550edd
commit c7dc74fce0
5 changed files with 148 additions and 26 deletions

View File

@@ -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()

View File

@@ -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
}

View File

@@ -5,7 +5,7 @@ import "log/slog"
type LoggingConfig struct {
Instance *slog.Logger
Level string
ShowCanDump bool
ShowCfgDump bool
}
type Config struct {

View 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
}
}

View File

@@ -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)