add logger, godotenv
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
Hosting by ValitovGaziz team on docker compose
|
Hosting by ValitovGaziz team on docker compose
|
||||||
|
|
||||||
create REST API on Golang 1.25.1
|
create REST API on Golang 1.25.1
|
||||||
|
|
||||||
|
### zap logger внедрить
|
||||||
@@ -5,4 +5,12 @@ DB_USER=postgres
|
|||||||
DB_PASSWORD=postgres
|
DB_PASSWORD=postgres
|
||||||
DB_NAME=bb_db
|
DB_NAME=bb_db
|
||||||
DB_SSLMODE=disable
|
DB_SSLMODE=disable
|
||||||
JWT_SECRET=your-super-secret-jwt-key-change-in-production
|
JWT_SECRET=your-super-secret-jwt-key-change-in-production
|
||||||
|
|
||||||
|
|
||||||
|
# .env
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
ENVIRONMENT=development
|
||||||
|
|
||||||
|
# app
|
||||||
|
REST_API_VERSION=1.0.0
|
||||||
@@ -1,37 +1,92 @@
|
|||||||
// main.go
|
// main.go с graceful shutdown
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"api_bb/internal/config"
|
"api_bb/internal/config"
|
||||||
"api_bb/internal/models"
|
"api_bb/internal/models"
|
||||||
"api_bb/internal/routes"
|
"api_bb/internal/routes"
|
||||||
|
"api_bb/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
// Загрузка конфигурации
|
// Загрузка конфигурации
|
||||||
cfg := config.Load()
|
cfg := config.Load()
|
||||||
|
|
||||||
|
// Инициализация логгера
|
||||||
|
if err := logger.Init(
|
||||||
|
os.Getenv("LOG_LEVEL"),
|
||||||
|
os.Getenv("ENVIRONMENT"),
|
||||||
|
); err != nil {
|
||||||
|
log.Printf("Failed to initialize logger: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer logger.Sync()
|
||||||
|
|
||||||
|
zapLogger := logger.Get()
|
||||||
|
|
||||||
|
// Логируем начало работы
|
||||||
|
logger.LogApplicationStart(os.Getenv("REST_API_VERSION"), os.Getenv("ENVIRONMENT"), "")
|
||||||
|
|
||||||
|
|
||||||
// Подключение к базе данных
|
// Подключение к базе данных
|
||||||
db, err := gorm.Open(postgres.Open(cfg.DatabaseURL), &gorm.Config{})
|
db, err := gorm.Open(postgres.Open(cfg.DatabaseURL), &gorm.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to connect to database:", err)
|
zapLogger.Fatal("failed to connect to database", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Автомиграция
|
// Автомиграция
|
||||||
if err := db.AutoMigrate(&models.User{}); err != nil {
|
if err := db.AutoMigrate(&models.User{}); err != nil {
|
||||||
log.Fatal("Failed to migrate database:", err)
|
zapLogger.Fatal("database migration failed", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Настройка роутера
|
// Настройка роутера
|
||||||
router := routes.SetupRouter(db, cfg)
|
router := routes.SetupRouter(db, cfg)
|
||||||
|
|
||||||
// Запуск сервера
|
// Настройка HTTP сервера
|
||||||
log.Printf("Server starting on port %s", cfg.Port)
|
server := &http.Server{
|
||||||
log.Fatal(http.ListenAndServe(":"+cfg.Port, router))
|
Addr: ":" + cfg.Port,
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Канал для graceful shutdown
|
||||||
|
done := make(chan bool, 1)
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
// Запуск сервера в горутине
|
||||||
|
go func() {
|
||||||
|
zapLogger.Info("starting HTTP server", zap.String("port", cfg.Port))
|
||||||
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
zapLogger.Fatal("failed to start server", zap.Error(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Ожидание сигнала shutdown
|
||||||
|
<-quit
|
||||||
|
zapLogger.Info("shutdown signal received")
|
||||||
|
|
||||||
|
// Graceful shutdown
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
server.SetKeepAlivesEnabled(false)
|
||||||
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
|
zapLogger.Fatal("could not gracefully shutdown the server", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogApplicationShutdown("graceful shutdown")
|
||||||
|
close(done)
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,8 @@ require (
|
|||||||
gorm.io/gorm v1.31.0
|
gorm.io/gorm v1.31.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require go.uber.org/multierr v1.10.0 // indirect
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
@@ -18,6 +20,8 @@ require (
|
|||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
go.uber.org/zap v1.27.0
|
||||||
golang.org/x/sync v0.17.0 // indirect
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
golang.org/x/text v0.30.0 // indirect
|
golang.org/x/text v0.30.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
|||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@@ -26,6 +28,10 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
|||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
|
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ package config
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -13,6 +15,7 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Load() *Config {
|
func Load() *Config {
|
||||||
|
_ = godotenv.Load(".env")
|
||||||
port := getEnv("PORT", "8080")
|
port := getEnv("PORT", "8080")
|
||||||
jwtSecret := getEnv("JWT_SECRET", "your-secret-key")
|
jwtSecret := getEnv("JWT_SECRET", "your-secret-key")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// pkg/logger/helpers.go
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogApplicationStart логирует запуск приложения
|
||||||
|
func LogApplicationStart(version, environment, port string) {
|
||||||
|
Get().Info("application starting",
|
||||||
|
zap.String("version", version),
|
||||||
|
zap.String("environment", environment),
|
||||||
|
zap.String("port", port),
|
||||||
|
zap.Time("start_time", time.Now()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogApplicationShutdown логирует graceful shutdown
|
||||||
|
func LogApplicationShutdown(reason string) {
|
||||||
|
Get().Info("application shutting down",
|
||||||
|
zap.String("reason", reason),
|
||||||
|
zap.Time("shutdown_time", time.Now()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogDatabaseStats логирует статистику базы данных
|
||||||
|
func LogDatabaseStats(stats map[string]interface{}) {
|
||||||
|
fields := make([]zap.Field, 0, len(stats))
|
||||||
|
for key, value := range stats {
|
||||||
|
fields = append(fields, zap.Any(key, value))
|
||||||
|
}
|
||||||
|
Get().Info("database statistics", fields...)
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// pkg/logger/interface.go
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import "go.uber.org/zap"
|
||||||
|
|
||||||
|
// Interface определяет контракт для логгера
|
||||||
|
type Interface interface {
|
||||||
|
Debug(msg string, fields ...zap.Field)
|
||||||
|
Info(msg string, fields ...zap.Field)
|
||||||
|
Warn(msg string, fields ...zap.Field)
|
||||||
|
Error(msg string, fields ...zap.Field)
|
||||||
|
Fatal(msg string, fields ...zap.Field)
|
||||||
|
|
||||||
|
Debugf(template string, args ...interface{})
|
||||||
|
Infof(template string, args ...interface{})
|
||||||
|
Warnf(template string, args ...interface{})
|
||||||
|
Errorf(template string, args ...interface{})
|
||||||
|
Fatalf(template string, args ...interface{})
|
||||||
|
|
||||||
|
With(fields ...zap.Field) Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapper обертка для zap.Logger
|
||||||
|
type wrapper struct {
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWrapper создает новую обертку
|
||||||
|
func NewWrapper(logger *zap.Logger) Interface {
|
||||||
|
return &wrapper{logger: logger}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Debug(msg string, fields ...zap.Field) {
|
||||||
|
w.logger.Debug(msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Info(msg string, fields ...zap.Field) {
|
||||||
|
w.logger.Info(msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Warn(msg string, fields ...zap.Field) {
|
||||||
|
w.logger.Warn(msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Error(msg string, fields ...zap.Field) {
|
||||||
|
w.logger.Error(msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Fatal(msg string, fields ...zap.Field) {
|
||||||
|
w.logger.Fatal(msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Debugf(template string, args ...interface{}) {
|
||||||
|
w.logger.Sugar().Debugf(template, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Infof(template string, args ...interface{}) {
|
||||||
|
w.logger.Sugar().Infof(template, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Warnf(template string, args ...interface{}) {
|
||||||
|
w.logger.Sugar().Warnf(template, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Errorf(template string, args ...interface{}) {
|
||||||
|
w.logger.Sugar().Errorf(template, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Fatalf(template string, args ...interface{}) {
|
||||||
|
w.logger.Sugar().Fatalf(template, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) With(fields ...zap.Field) Interface {
|
||||||
|
return &wrapper{logger: w.logger.With(fields...)}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// pkg/logger/logger.go
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
var globalLogger *zap.Logger
|
||||||
|
|
||||||
|
// Init инициализирует глобальный логгер
|
||||||
|
func Init(level string, environment string) error {
|
||||||
|
var config zap.Config
|
||||||
|
|
||||||
|
if environment == "production" {
|
||||||
|
config = zap.NewProductionConfig()
|
||||||
|
} else {
|
||||||
|
config = zap.NewDevelopmentConfig()
|
||||||
|
config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Устанавливаем уровень логирования
|
||||||
|
switch level {
|
||||||
|
case "debug":
|
||||||
|
config.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
|
||||||
|
case "info":
|
||||||
|
config.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
|
||||||
|
case "warn":
|
||||||
|
config.Level = zap.NewAtomicLevelAt(zap.WarnLevel)
|
||||||
|
case "error":
|
||||||
|
config.Level = zap.NewAtomicLevelAt(zap.ErrorLevel)
|
||||||
|
default:
|
||||||
|
config.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, err := config.Build()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
globalLogger = logger
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get возвращает глобальный логгер
|
||||||
|
func Get() *zap.Logger {
|
||||||
|
if globalLogger == nil {
|
||||||
|
// Fallback на стандартный логгер если не инициализирован
|
||||||
|
logger, _ := zap.NewProduction()
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
return globalLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync синхронизирует буферы логгера
|
||||||
|
func Sync() {
|
||||||
|
if globalLogger != nil {
|
||||||
|
globalLogger.Sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sugar возвращает SugaredLogger
|
||||||
|
func Sugar() *zap.SugaredLogger {
|
||||||
|
return Get().Sugar()
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// pkg/middleware/logger.go
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"api_bb/pkg/logger"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger middleware для логирования HTTP запросов
|
||||||
|
func Logger(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
// Получаем request ID
|
||||||
|
reqID := middleware.GetReqID(r.Context())
|
||||||
|
|
||||||
|
// Создаем логгер с контекстом запроса
|
||||||
|
requestLogger := logger.Get().With(
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
zap.String("user_agent", r.UserAgent()),
|
||||||
|
zap.String("request_id", reqID),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Обертываем ResponseWriter для получения статуса
|
||||||
|
wrappedWriter := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
|
||||||
|
|
||||||
|
// Обрабатываем запрос
|
||||||
|
next.ServeHTTP(wrappedWriter, r)
|
||||||
|
|
||||||
|
// Логируем результат
|
||||||
|
duration := time.Since(start)
|
||||||
|
|
||||||
|
requestLogger.Info("request completed",
|
||||||
|
zap.Int("status", wrappedWriter.Status()),
|
||||||
|
zap.Int("bytes", wrappedWriter.BytesWritten()),
|
||||||
|
zap.Duration("duration", duration),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user