diff --git a/.env b/.env index 6992bbb..2209bee 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ #Configuración de la base de datos -DB_HOST=localhost -DB_PORT=5434 +DB_HOST=db +DB_PORT=5432 DB_USER=admin DB_PASSWORD=admin DB_NAME=gotestdb @@ -8,7 +8,7 @@ DB_MAX_OPEN_CONNS=25 DB_MAX_IDLE_CONNS=10 DB_CONN_MAX_LIFETIME=30m #Redis -REDIS_HOST=localhost +REDIS_HOST=redis REDIS_PORT=6379 REDIS_PASSWORD=TuPasswordSegura123 REDIS_DB=0 @@ -16,18 +16,18 @@ REDIS_TTL=10m REDIS_SUBSCRIBE=cron:reload #Log LOG_FILE_PATH=logs/syncronizador.log -LOG_LEVEL=debug +LOG_LEVEL=info LOG_MAX_SIZE=10 LOG_MAX_BACKUPS=7 LOG_MAX_AGE=30 LOG_COMPRESS=true #Cliente Rest -TIMEOUT_SECONDS= 30 +TIMEOUT_SECONDS=30 RETRY_COUNT=3 TLS_SKIP_VERIFY=true LOG_REQUESTS=false -ENABLE_DEBUG= false +ENABLE_DEBUG=false WHERE_UNITS_BUSINESS=company_name = @company_name AND company_db = @company_db AND status = 'A' @@ -41,13 +41,13 @@ ENCRYPTION_KEY=12345678901234567890123456789012 -AUTH_ENDPOINT= http://localhost:8085/auth/headers -AUTH_AUTORIZATION= 'Basic d29ya2VyLXJlbmRpY2lvbjpwcnVlYmE=' +AUTH_ENDPOINT=http://authsvc:8080/auth/headers +AUTH_AUTORIZATION='Basic d29ya2VyLXJlbmRpY2lvbjpwcnVlYmE=' AUTH_METHOD=POST TM_HEADER_ORIGIN=https://azure-function.timemanagerweb.com TM_HEADER_TENANT_NAME=pruebas-dos -TM_HEADER_USER_AGENT='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' +TM_HEADER_USER_AGENT=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 # Configuración de correo SMTP diff --git a/cmd/go-sync-service/main.go b/cmd/go-sync-service/main.go index 04f9ffc..f7d0919 100644 --- a/cmd/go-sync-service/main.go +++ b/cmd/go-sync-service/main.go @@ -7,6 +7,7 @@ import ( "os" "os/signal" "syscall" + "time" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/tuusuario/go-sync-service/internal/config" @@ -16,25 +17,28 @@ import ( ) func main() { + // Configurar zona horaria por defecto del proceso + initTimezone() + // Cargar configuración conf, err := config.LoadConfig() if err != nil { - fmt.Println("❌ Error cargando configuración:", err) + fmt.Println("Error cargando configuración:", err) os.Exit(1) } // Inicializar logger con configuración config.InitLogger(conf) - config.Log.Info("🚀 Iniciando servicio: go-sync-service") + config.Log.Info("Iniciando servicio: go-sync-service") emailService := email.NewSMTPEmailService(conf) // Conexión a Redis redisClient := config.GetRedisClient(conf) if err := redisClient.Ping(context.Background()).Err(); err != nil { - config.Log.Fatalf("❌ Redis no disponible: %v", err) + config.Log.Fatalf("Redis no disponible: %v", err) } - config.Log.Info("✅ Redis conectado") + config.Log.Info("Redis conectado") // Crear proveedor de configuración desde Redis redisManager := config.NewRedisManager(redisClient) @@ -43,14 +47,14 @@ func main() { // Conexión a Base de Datos database := config.GetDatabaseConnection(conf) if database == nil { - config.Log.Fatal("❌ No se pudo establecer la conexión con la base de datos.") + config.Log.Fatal("No se pudo establecer la conexión con la base de datos.") } - config.Log.Info("✅ Conexión a base de datos establecida") + config.Log.Info("Conexión a base de datos establecida") scheduler.Start(context.Background(), redisClient, redisConfigProvider, database, *emailService) - config.Log.Info("✅ Scheduler en ejecución y escuchando recargas") + config.Log.Info("Scheduler en ejecución y escuchando recargas") - //metrics Grafana + // Metrics Grafana/Prometheus metrics.Register() startMetricsServer() @@ -59,13 +63,24 @@ func main() { signal.Notify(stop, os.Interrupt, syscall.SIGTERM) <-stop - config.Log.Info("🛑 Señal de apagado recibida, cerrando servicio...") + config.Log.Info("Señal de apagado recibida, cerrando servicio...") +} + +func initTimezone() { + loc, err := time.LoadLocation("America/La_Paz") + if err != nil { + // Si falla la carga, mantenemos la zona por defecto del entorno + // y solo registramos el problema en stdout para no depender del logger aún. + fmt.Println("No se pudo cargar la zona horaria America/La_Paz:", err) + return + } + time.Local = loc } func startMetricsServer() { go func() { http.Handle("/metrics", promhttp.Handler()) - config.Log.Info("📊 Servidor de métricas en :9100/metrics") - http.ListenAndServe(":9100", nil) + config.Log.Info("Servidor de métricas en :9100/metrics") + _ = http.ListenAndServe(":9100", nil) }() } diff --git a/docker-compose.yml b/docker-compose.yml index d465d19..547246f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,10 @@ services: syncronizador: container_name: rendicion_gasto_syncronizador + environment: + - TZ=America/La_Paz + - LANG=es_BO.UTF-8 + - LC_ALL=es_BO.UTF-8 build: context: . dockerfile: Dockerfile @@ -11,7 +15,12 @@ services: - ./logs:/app/logs networks: - network_emba - + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + compress: "true" networks: network_emba: external: true diff --git a/internal/config/logger.go b/internal/config/logger.go index 635a85e..a9d0753 100644 --- a/internal/config/logger.go +++ b/internal/config/logger.go @@ -17,9 +17,34 @@ import ( // Log instancia global del logger var Log = logrus.New() +type LocalTimeHook struct { + loc *time.Location +} +func (h LocalTimeHook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func (h LocalTimeHook) Fire(e *logrus.Entry) error { + if h.loc != nil { + e.Time = e.Time.In(h.loc) + } + return nil +} func InitLogger(cfg *Config) { +// ========================= + // Zona horaria Bolivia + // ========================= + loc, err := time.LoadLocation("America/La_Paz") + if err != nil { + // Fallback robusto para contenedores sin tzdata + loc = time.FixedZone("America/La_Paz", -4*60*60) + } + time.Local = loc + + // Hook para forzar timestamps de Logrus a hora Bolivia + Log.AddHook(LocalTimeHook{loc: loc}) // Configurar rotación de logs con Lumberjack rotator := &lumberjack.Logger{ Filename: cfg.LogFilePath, // Archivo de logs @@ -56,7 +81,7 @@ func InitLogger(cfg *Config) { //elastic if cfg.ElasticEnabled { - Log.Debug("✅ Elasticsearch enabled") + Log.Info("✅ Elasticsearch enabled") es, err := elasticsearch.NewClient(elasticsearch.Config{ Addresses: []string{cfg.ElasticURL}, }) diff --git a/internal/db/operaciones.go b/internal/db/operaciones.go index 549237c..69cfc34 100644 --- a/internal/db/operaciones.go +++ b/internal/db/operaciones.go @@ -64,7 +64,7 @@ func (g *GormDatabase) SyncRows(persistencia dto.Persistencia, rawData *[]map[st // Procesar lote if len(batch) == batchSize || i == len(*rawData)-1 { - config.Log.Debugf(logPrefix+" Procesando batch de %d registros", len(batch)) + config.Log.Println(logPrefix+" Procesando batch de %d registros", len(batch)) if len(persistencia.UpdateBy) > 0 { // Updates con múltiples campos @@ -81,7 +81,7 @@ func (g *GormDatabase) SyncRows(persistencia dto.Persistencia, rawData *[]map[st whereValues = append(whereValues, val) } if len(whereParts) < len(persistencia.UpdateBy) { - config.Log.Warnf("⚠️ Registro incompleto para update (faltan claves): %+v", row) + config.Log.Debugf("⚠️ Registro incompleto para update (faltan claves): %+v", row) continue } @@ -108,10 +108,10 @@ func (g *GormDatabase) SyncRows(persistencia dto.Persistencia, rawData *[]map[st config.Log.Errorf("%s ❌ Error en update: %v", logPrefix, res.Error) return res.Error } - if res.RowsAffected == 0 { - config.Log.Warnf("%s ⚠️ Ninguna fila afectada con campos: %v valores: %v", + /* if res.RowsAffected == 0 { + config.Log.Debugf("%s ⚠️ Ninguna fila afectada con campos: %v valores: %v", logPrefix, strings.Join(whereParts, " AND "), printWhereValues(whereValues)) - } + } */ } } else { // Inserts con conflicto por PK (UPSERT) @@ -173,17 +173,17 @@ func (g *GormDatabase) GetCredencialesFromTemplate(whereTemplate string, variabl query := whereTemplate var args []interface{} - config.Log.Debugf("🔎 Variables recibidas:") + config.Log.Info("🔎 Variables recibidas:") for k, v := range variables { placeholder := "@" + k query = strings.ReplaceAll(query, placeholder, "?") args = append(args, v) - config.Log.Debugf(" %s = %v", k, v) + config.Log.Infof(" %s = %v", k, v) } - config.Log.Debugf("📝 Consulta final construida:") - config.Log.Debugf(" Query: %s", query) - config.Log.Debugf(" Args: %v", args) + config.Log.Info("📝 Consulta final construida:") + config.Log.Infof(" Query: %s", query) + config.Log.Infof(" Args: %v", args) err := g.db.Where(query, args...).First(&cred).Error return &cred, err diff --git a/internal/http/session.go b/internal/http/session.go index b5336a0..a4c1ec7 100644 --- a/internal/http/session.go +++ b/internal/http/session.go @@ -155,13 +155,13 @@ func prepareAuthBody(auth dto.ServiceConfig, cred *model.CredencialesSAP, logPre } if auth.GQL { - config.Log.Debugf("%v 🧠 Preparando auth para GraphQL", logPrefix) + config.Log.Infof("%v 🧠 Preparando auth para GraphQL", logPrefix) auth.Rest.Body = map[string]string{ "username": cred.UserName, "password": cred.Password, } } else { - config.Log.Debugf("%v🧠 Preparando auth para REST", logPrefix) + config.Log.Infof("%v🧠 Preparando auth para REST", logPrefix) auth.Rest.Body = map[string]string{ "CompanyDB": cred.CompanyDB, "UserName": cred.UserName, diff --git a/internal/sync/fetcher.go b/internal/sync/fetcher.go index 5b17a68..6136b4f 100644 --- a/internal/sync/fetcher.go +++ b/internal/sync/fetcher.go @@ -3,11 +3,12 @@ package fetcher import ( "encoding/json" "fmt" - "github.com/tuusuario/go-sync-service/internal/email" "strconv" "strings" "time" + "github.com/tuusuario/go-sync-service/internal/email" + "github.com/tuusuario/go-sync-service/internal/config" "github.com/tuusuario/go-sync-service/internal/db" "github.com/tuusuario/go-sync-service/internal/domain/dto" @@ -122,6 +123,9 @@ func FetchAllPaginatedManual[T any](host string, service dto.ServiceConfig, logP return nil, fmt.Errorf("%s ❌ error en la petición: %w", logPrefix, err) } + // DEBUG: imprimir lo que devuelve el servicio + config.Log.Debugf("%s Body recibido (REST paginado): %s", logPrefix, string(resp.Body())) + var result struct { Value []T `json:"value"` } @@ -206,6 +210,10 @@ func FetchAllPaginatedManual[T any](host string, service dto.ServiceConfig, logP if err != nil { return nil, fmt.Errorf("%s ❌ error en la petición: %w", logPrefix, err) } + + // DEBUG: imprimir lo que devuelve el servicio sin paginación + config.Log.Debugf("%s Body recibido (REST sin paginación): %s", logPrefix, string(resp.Body())) + var result struct { Value []T `json:"value"` } diff --git a/internal/utils/redis_loader.go b/internal/utils/redis_loader.go index d22bc77..df0b7f4 100644 --- a/internal/utils/redis_loader.go +++ b/internal/utils/redis_loader.go @@ -17,7 +17,7 @@ func CargarDesdeRedis[T any](cfg ports.RedisConfigProvider, clave string) (*T, e return nil, fmt.Errorf("error al obtener clave [%s] de redis: %w", clave, err) } - config.Log.Debugf("🔑 Clave [%s] obtenida de Redis: %s", clave, data) + config.Log.Infof("🔑 Clave [%s] obtenida de Redis: %s", clave, data) var result T if err := json.Unmarshal([]byte(data), &result); err != nil { config.Log.Errorf("❌ error al parsear JSON [%s]: %s", clave, err)