2025-05-17 11:36:26 -04:00

875 lines
24 KiB
Go

package logger
import (
"api-soap-facturacion/internal/config"
"encoding/json"
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
lumberjack "gopkg.in/natefinch/lumberjack.v2"
"os"
"strings"
)
// Logger es una interfaz que define los métodos necesarios para el logging
type Logger interface {
Debug(msg string, fields ...Field)
Info(msg string, fields ...Field)
Warn(msg string, fields ...Field)
Error(msg string, fields ...Field)
Fatal(msg string, fields ...Field)
With(fields ...Field) Logger
}
// Field es un campo para agregar contexto a los logs
type Field struct {
Key string
Value interface{}
}
// zapLogger implementa la interfaz Logger usando zap
type zapLogger struct {
logger *zap.Logger
}
// CustomEncoder implementa la interfaz zapcore.Encoder
type CustomEncoder struct {
zapcore.Encoder
pool buffer.Pool
serviceName string
}
// NewCustomEncoder crea un nuevo encoder personalizado
func NewCustomEncoder(cfg zapcore.EncoderConfig, serviceName string) zapcore.Encoder {
return &CustomEncoder{
Encoder: zapcore.NewJSONEncoder(cfg),
pool: buffer.NewPool(),
serviceName: serviceName,
}
}
// EncodeEntry implementa el formato personalizado
// EncodeEntry implementa el formato personalizado
func (e *CustomEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
line := e.pool.Get()
// Agregar nivel (INFO:, WARN:, etc.)
levelStr := entry.Level.CapitalString()
line.AppendString(levelStr)
line.AppendString(": ")
// Agregar timestamp
timestamp := entry.Time.Format("2006/01/02 15:04:05")
line.AppendString(timestamp)
line.AppendString(" ")
// Agregar información del archivo/línea si está disponible
if entry.Caller.Defined {
// Convertir la ruta a formato Unix para manejar uniformemente las barras
filePath := strings.ReplaceAll(entry.Caller.File, "\\", "/")
// Opciones para encontrar la parte relevante del path
possiblePrefixes := []string{
"/cmd/",
"/internal/",
"/pkg/",
"/api/",
"/app/",
"/src/",
}
// Buscar el prefijo más cercano al final del path
shortPath := filePath
for _, prefix := range possiblePrefixes {
if idx := strings.LastIndex(filePath, prefix); idx != -1 {
// Encontramos un prefijo estándar de Go
shortPath = filePath[idx+1:] // +1 para quitar el slash inicial
break
}
}
// Si no se encontró ningún prefijo, usar el último componente del path
if shortPath == filePath {
parts := strings.Split(filePath, "/")
if len(parts) > 0 {
shortPath = parts[len(parts)-1]
}
}
caller := fmt.Sprintf("%s:%d: ", shortPath, entry.Caller.Line)
line.AppendString(caller)
}
// Agregar nombre del servicio entre corchetes
line.AppendString("[")
line.AppendString(e.serviceName)
line.AppendString("] ")
// Agregar mensaje
line.AppendString(entry.Message)
// Procesar campos adicionales
if len(fields) > 0 {
// Usar encoder JSON para serializar los campos
encoder := zapcore.NewMapObjectEncoder()
// Agregar cada campo al encoder, excepto "service"
for _, field := range fields {
if field.Key != "service" {
field.AddTo(encoder)
}
}
// Solo mostramos los campos si hay alguno después de filtrar
if len(encoder.Fields) > 0 {
line.AppendString(" ")
fieldsJSON, err := json.Marshal(encoder.Fields)
if err == nil {
line.AppendString(string(fieldsJSON))
}
}
}
line.AppendString("\n")
return line, nil
}
//func (e *CustomEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
// line := e.pool.Get()
//
// // Agregar nivel (INFO:, WARN:, etc.)
// levelStr := entry.Level.CapitalString()
// line.AppendString(levelStr)
// line.AppendString(": ")
//
// // Agregar timestamp
// timestamp := entry.Time.Format("2006/01/02 15:04:05")
// line.AppendString(timestamp)
// line.AppendString(" ")
//
// // Agregar información del archivo/línea si está disponible
// if entry.Caller.Defined {
// // Obtener solo la parte relevante de la ruta (después del último "api-soap-facturacion")
// filePath := entry.Caller.File
// if idx := strings.LastIndex(filePath, "api-soap-facturacion"); idx != -1 {
// // Si encontramos la cadena, tomamos lo que viene después más "api-soap-facturacion"
// filePath = filePath[idx:]
// } else {
// // Si no encontramos la cadena, intentamos tomar solo el nombre del archivo
// if idx := strings.LastIndex(filePath, "/"); idx != -1 {
// filePath = filePath[idx+1:]
// } else if idx := strings.LastIndex(filePath, "\\"); idx != -1 {
// filePath = filePath[idx+1:]
// }
// }
//
// caller := fmt.Sprintf("%s:%d: ", filePath, entry.Caller.Line)
// line.AppendString(caller)
// }
// // Agregar nombre del servicio entre corchetes
// line.AppendString("[")
// line.AppendString(e.serviceName)
// line.AppendString("] ")
//
// // Agregar mensaje
// line.AppendString(entry.Message)
//
// // Procesar campos adicionales
// if len(fields) > 0 {
// // Usar encoder JSON para serializar los campos
// encoder := zapcore.NewMapObjectEncoder()
//
// // Agregar cada campo al encoder, excepto "service"
// for _, field := range fields {
// if field.Key != "service" {
// field.AddTo(encoder)
// }
// }
//
// // Solo mostramos los campos si hay alguno después de filtrar
// if len(encoder.Fields) > 0 {
// line.AppendString(" ")
// fieldsJSON, err := json.Marshal(encoder.Fields)
// if err == nil {
// line.AppendString(string(fieldsJSON))
// }
// }
// }
//
// line.AppendString("\n")
// return line, nil
//}
//func (e *CustomEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
// line := e.pool.Get()
//
// // Agregar nivel (INFO:, WARN:, etc.)
// levelStr := entry.Level.CapitalString()
// line.AppendString(levelStr)
// line.AppendString(": ")
//
// // Agregar timestamp
// timestamp := entry.Time.Format("2006/01/02 15:04:05")
// line.AppendString(timestamp)
// line.AppendString(" ")
//
// // Agregar información del archivo/línea si está disponible
// if entry.Caller.Defined {
// caller := fmt.Sprintf("%s:%d: ", entry.Caller.File, entry.Caller.Line)
// line.AppendString(caller)
// }
//
// // Agregar nombre del servicio entre corchetes
// line.AppendString("[")
// line.AppendString(e.serviceName)
// line.AppendString("] ")
//
// // Agregar mensaje
// line.AppendString(entry.Message)
//
// // Procesar campos adicionales
// if len(fields) > 0 {
// // Usar encoder JSON para serializar los campos
// encoder := zapcore.NewMapObjectEncoder()
//
// // Agregar cada campo al encoder, excepto "service"
// for _, field := range fields {
// if field.Key != "service" {
// field.AddTo(encoder)
// }
// }
//
// // Solo mostramos los campos si hay alguno después de filtrar
// if len(encoder.Fields) > 0 {
// line.AppendString(" ")
// fieldsJSON, err := json.Marshal(encoder.Fields)
// if err == nil {
// line.AppendString(string(fieldsJSON))
// }
// }
// }
//
// line.AppendString("\n")
// return line, nil
//}
// Clone implementa la interfaz Encoder
func (e *CustomEncoder) Clone() zapcore.Encoder {
return &CustomEncoder{
Encoder: e.Encoder.Clone(),
pool: e.pool,
serviceName: e.serviceName,
}
}
// NewLogger crea una nueva instancia de Logger
func NewLogger(cfg *config.Config) Logger {
// Configurar nivel de log
var level zapcore.Level
switch cfg.App.LogLevel {
case "debug":
level = zapcore.DebugLevel
case "info":
level = zapcore.InfoLevel
case "warn":
level = zapcore.WarnLevel
case "error":
level = zapcore.ErrorLevel
default:
level = zapcore.InfoLevel
}
// Configuración básica para el encoder
encoderConfig := zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
// Crear el encoder personalizado
customEncoder := NewCustomEncoder(encoderConfig, cfg.App.Name)
// Configurar el output
var core zapcore.Core
if cfg.App.LogFilePath != "" {
// Si se usa archivo con lumberjack
logWriter := &lumberjack.Logger{
Filename: cfg.App.LogFilePath,
MaxSize: cfg.App.LogMaxSize,
MaxBackups: cfg.App.LogMaxBackups,
MaxAge: cfg.App.LogMaxAge,
Compress: cfg.App.LogCompress,
}
core = zapcore.NewCore(
customEncoder,
zapcore.AddSync(logWriter),
level,
)
} else {
// Solo usar stdout
core = zapcore.NewCore(
customEncoder,
zapcore.AddSync(os.Stdout),
level,
)
}
// Habilitar el caller para mostrar información del archivo y línea
zapLog := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
// Ya no agregamos el nombre del servicio como campo, ya que lo incluimos en el formato
// zapLog = zapLog.With(zap.String("service", cfg.App.Name))
return &zapLogger{logger: zapLog}
}
// NewField crea un campo para agregar a los logs
func NewField(key string, value interface{}) Field {
return Field{Key: key, Value: value}
}
// convertFields convierte nuestros campos a campos de zap
func (l *zapLogger) convertFields(fields []Field) []zap.Field {
zapFields := make([]zap.Field, len(fields))
for i, field := range fields {
zapFields[i] = zap.Any(field.Key, field.Value)
}
return zapFields
}
// Debug registra un mensaje con nivel Debug
func (l *zapLogger) Debug(msg string, fields ...Field) {
l.logger.Debug(msg, l.convertFields(fields)...)
}
// Info registra un mensaje con nivel Info
func (l *zapLogger) Info(msg string, fields ...Field) {
l.logger.Info(msg, l.convertFields(fields)...)
}
// Warn registra un mensaje con nivel Warn
func (l *zapLogger) Warn(msg string, fields ...Field) {
l.logger.Warn(msg, l.convertFields(fields)...)
}
// Error registra un mensaje con nivel Error
func (l *zapLogger) Error(msg string, fields ...Field) {
l.logger.Error(msg, l.convertFields(fields)...)
}
// Fatal registra un mensaje con nivel Fatal
func (l *zapLogger) Fatal(msg string, fields ...Field) {
l.logger.Fatal(msg, l.convertFields(fields)...)
}
// With retorna un nuevo logger con campos adicionales
func (l *zapLogger) With(fields ...Field) Logger {
return &zapLogger{
logger: l.logger.With(l.convertFields(fields)...),
}
}
//package logger
//
//import (
// "api-soap-facturacion/internal/config"
// "encoding/json"
// "fmt"
// "go.uber.org/zap"
// "go.uber.org/zap/zapcore"
// lumberjack "gopkg.in/natefinch/lumberjack.v2" // Usa este alias
// "os"
//)
//
//// Logger es una interfaz que define los métodos necesarios para el logging
//type Logger interface {
// Debug(msg string, fields ...Field)
// Info(msg string, fields ...Field)
// Warn(msg string, fields ...Field)
// Error(msg string, fields ...Field)
// Fatal(msg string, fields ...Field)
// With(fields ...Field) Logger
//}
//
//// Field es un campo para agregar contexto a los logs
//type Field struct {
// Key string
// Value interface{}
//}
//
//// zapLogger implementa la interfaz Logger usando zap
//type zapLogger struct {
// logger *zap.Logger
//}
//
//// CustomEncoder implementa la interfaz zapcore.Encoder
//type CustomEncoder struct {
// zapcore.Encoder
// pool buffer.Pool
// serviceName string
//}
//
//// NewCustomEncoder crea un nuevo encoder personalizado
//func NewCustomEncoder(cfg zapcore.EncoderConfig, serviceName string) zapcore.Encoder {
// return &CustomEncoder{
// Encoder: zapcore.NewJSONEncoder(cfg),
// pool: buffer.NewPool(),
// serviceName: serviceName,
// }
//}
//
//// EncodeEntry implementa el formato personalizado
//func (e *CustomEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
// line := e.pool.Get()
//
// // Agregar nivel (INFO:, WARN:, etc.)
// levelStr := entry.Level.CapitalString()
// line.AppendString(levelStr)
// line.AppendString(": ")
//
// // Agregar timestamp
// timestamp := entry.Time.Format("2006/01/02 15:04:05")
// line.AppendString(timestamp)
// line.AppendString(" ")
//
// // Agregar información del archivo/línea si está disponible
// if entry.Caller.Defined {
// caller := fmt.Sprintf("%s:%d: ", entry.Caller.File, entry.Caller.Line)
// line.AppendString(caller)
// }
//
// // Agregar nombre del servicio entre corchetes
// line.AppendString("[")
// line.AppendString(e.serviceName)
// line.AppendString("] ")
//
// // Agregar mensaje
// line.AppendString(entry.Message)
//
// // Procesar campos adicionales
// if len(fields) > 0 {
// line.AppendString(" ")
// fieldsMap := make(map[string]interface{})
// for _, field := range fields {
// if field.Key != "service" {
// field.AddTo(fieldsMap)
// }
// }
//
// if len(fieldsMap) > 0 {
// fieldJSON, err := json.Marshal(fieldsMap)
// if err == nil {
// line.AppendString(string(fieldJSON))
// }
// }
// }
//
// line.AppendString("\n")
// return line, nil
//}
//
//// Clone implementa la interfaz Encoder
//func (e *CustomEncoder) Clone() zapcore.Encoder {
// return &CustomEncoder{
// Encoder: e.Encoder.Clone(),
// pool: e.pool,
// serviceName: e.serviceName,
// }
//}
//
//// NewLogger crea una nueva instancia de Logger tipo json
////func NewLogger(cfg *config.Config) Logger {
//// // Configurar nivel de log
//// var level zapcore.Level
//// switch cfg.App.LogLevel {
//// case "debug":
//// level = zapcore.DebugLevel
//// case "info":
//// level = zapcore.InfoLevel
//// case "warn":
//// level = zapcore.WarnLevel
//// case "error":
//// level = zapcore.ErrorLevel
//// default:
//// level = zapcore.InfoLevel
//// }
////
//// // Configurar encoderConfig
//// encoderConfig := zapcore.EncoderConfig{
//// TimeKey: "time",
//// LevelKey: "level",
//// NameKey: "logger",
//// CallerKey: "caller",
//// MessageKey: "msg",
//// StacktraceKey: "stacktrace",
//// LineEnding: zapcore.DefaultLineEnding,
//// EncodeLevel: zapcore.LowercaseLevelEncoder,
//// EncodeTime: zapcore.ISO8601TimeEncoder,
//// EncodeDuration: zapcore.SecondsDurationEncoder,
//// EncodeCaller: zapcore.ShortCallerEncoder,
//// }
////
//// // Crear escritores
//// var cores []zapcore.Core
////
//// // Siempre escribir a stdout
//// cores = append(cores, zapcore.NewCore(
//// zapcore.NewJSONEncoder(encoderConfig),
//// zapcore.AddSync(os.Stdout),
//// level,
//// ))
////
//// // Si se especificó una ruta de archivo de logs, configurar rotación
//// if cfg.App.LogFilePath != "" {
//// logWriter := &lumberjack.Logger{
//// Filename: cfg.App.LogFilePath,
//// MaxSize: cfg.App.LogMaxSize,
//// MaxBackups: cfg.App.LogMaxBackups,
//// MaxAge: cfg.App.LogMaxAge,
//// Compress: cfg.App.LogCompress,
//// }
////
//// cores = append(cores, zapcore.NewCore(
//// zapcore.NewJSONEncoder(encoderConfig),
//// zapcore.AddSync(logWriter),
//// level,
//// ))
//// }
////
//// // Combinar cores
//// core := zapcore.NewTee(cores...)
////
//// // Crear logger
//// zapLog := zap.New(core)
//// zapLog = zapLog.With(zap.String("service", cfg.App.Name))
////
//// return &zapLogger{logger: zapLog}
////}
//
//// fomato de texto mas flexible
//// NewLogger crea una nueva instancia de Logger
////func NewLogger(cfg *config.Config) Logger {
//// // Configurar nivel de log
//// var level zapcore.Level
//// switch cfg.App.LogLevel {
//// case "debug":
//// level = zapcore.DebugLevel
//// case "info":
//// level = zapcore.InfoLevel
//// case "warn":
//// level = zapcore.WarnLevel
//// case "error":
//// level = zapcore.ErrorLevel
//// default:
//// level = zapcore.InfoLevel
//// }
////
//// // Configurar encoderConfig para un formato más legible
//// encoderConfig := zapcore.EncoderConfig{
//// TimeKey: "time",
//// LevelKey: "level",
//// NameKey: "logger",
//// CallerKey: "caller",
//// FunctionKey: zapcore.OmitKey,
//// MessageKey: "msg",
//// StacktraceKey: "stacktrace",
//// LineEnding: zapcore.DefaultLineEnding,
//// EncodeLevel: zapcore.CapitalLevelEncoder, // INFO en lugar de info
//// EncodeTime: zapcore.TimeEncoderOfLayout("2006/01/02 15:04:05"), // Formato como 2025/05/15 00:13:07
//// EncodeDuration: zapcore.SecondsDurationEncoder,
//// EncodeCaller: zapcore.ShortCallerEncoder,
//// }
////
//// // Crear un formato personalizado
//// consoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
////
//// // Configurar el output final
//// var core zapcore.Core
//// if cfg.App.LogFilePath != "" && cfg.App.UseFileLogger {
//// // Si se usa archivo con lumberjack
//// logWriter := &lumberjack.Logger{
//// Filename: cfg.App.LogFilePath,
//// MaxSize: cfg.App.LogMaxSize,
//// MaxBackups: cfg.App.LogMaxBackups,
//// MaxAge: cfg.App.LogMaxAge,
//// Compress: cfg.App.LogCompress,
//// }
////
//// core = zapcore.NewCore(
//// consoleEncoder,
//// zapcore.AddSync(logWriter),
//// level,
//// )
//// } else {
//// // Solo usar stdout
//// core = zapcore.NewCore(
//// consoleEncoder,
//// zapcore.AddSync(os.Stdout),
//// level,
//// )
//// }
////
//// // Habilitar el caller para mostrar información del archivo y línea
//// zapLog := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
////
//// // Agregar nombre del servicio como prefijo constante
//// zapLog = zapLog.With(zap.String("service", cfg.App.Name))
////
//// return &zapLogger{logger: zapLog}
////}
//
//// NewLogger crea una nueva instancia de Logger
//func NewLogger(cfg *config.Config) Logger {
// // Configurar nivel de log
// var level zapcore.Level
// switch cfg.App.LogLevel {
// case "debug":
// level = zapcore.DebugLevel
// case "info":
// level = zapcore.InfoLevel
// case "warn":
// level = zapcore.WarnLevel
// case "error":
// level = zapcore.ErrorLevel
// default:
// level = zapcore.InfoLevel
// }
//
// // Configuración básica para el encoder
// encoderConfig := zapcore.EncoderConfig{
// TimeKey: "time",
// LevelKey: "level",
// NameKey: "logger",
// CallerKey: "caller",
// FunctionKey: zapcore.OmitKey,
// MessageKey: "msg",
// StacktraceKey: "stacktrace",
// LineEnding: zapcore.DefaultLineEnding,
// EncodeLevel: zapcore.CapitalLevelEncoder,
// EncodeTime: zapcore.ISO8601TimeEncoder,
// EncodeDuration: zapcore.SecondsDurationEncoder,
// EncodeCaller: zapcore.ShortCallerEncoder,
// }
//
// // Crear el encoder personalizado
// customEncoder := NewCustomEncoder(encoderConfig, cfg.App.Name)
//
// // Configurar el output
// var core zapcore.Core
// if cfg.App.LogFilePath != "" {
// // Si se usa archivo con lumberjack
// logWriter := &lumberjack.Logger{
// Filename: cfg.App.LogFilePath,
// MaxSize: cfg.App.LogMaxSize,
// MaxBackups: cfg.App.LogMaxBackups,
// MaxAge: cfg.App.LogMaxAge,
// Compress: cfg.App.LogCompress,
// }
//
// core = zapcore.NewCore(
// customEncoder,
// zapcore.AddSync(logWriter),
// level,
// )
// } else {
// // Solo usar stdout
// core = zapcore.NewCore(
// customEncoder,
// zapcore.AddSync(os.Stdout),
// level,
// )
// }
//
// // Habilitar el caller para mostrar información del archivo y línea
// zapLog := zap.New(core, zap.AddCaller())
//
// // Ya no agregamos el nombre del servicio como campo, ya que lo incluimos en el formato
// // zapLog = zapLog.With(zap.String("service", cfg.App.Name))
//
// return &zapLogger{logger: zapLog}
//}
//
//// NewField crea un campo para agregar a los logs
//func NewField(key string, value interface{}) Field {
// return Field{Key: key, Value: value}
//}
//
//// convertFields convierte nuestros campos a campos de zap
//func (l *zapLogger) convertFields(fields []Field) []zap.Field {
// zapFields := make([]zap.Field, len(fields))
// for i, field := range fields {
// zapFields[i] = zap.Any(field.Key, field.Value)
// }
// return zapFields
//}
//
//// Debug registra un mensaje con nivel Debug
//func (l *zapLogger) Debug(msg string, fields ...Field) {
// l.logger.Debug(msg, l.convertFields(fields)...)
//}
//
//// Info registra un mensaje con nivel Info
//func (l *zapLogger) Info(msg string, fields ...Field) {
// l.logger.Info(msg, l.convertFields(fields)...)
//}
//
//// Warn registra un mensaje con nivel Warn
//func (l *zapLogger) Warn(msg string, fields ...Field) {
// l.logger.Warn(msg, l.convertFields(fields)...)
//}
//
//// Error registra un mensaje con nivel Error
//func (l *zapLogger) Error(msg string, fields ...Field) {
// l.logger.Error(msg, l.convertFields(fields)...)
//}
//
//// Fatal registra un mensaje con nivel Fatal
//func (l *zapLogger) Fatal(msg string, fields ...Field) {
// l.logger.Fatal(msg, l.convertFields(fields)...)
//}
//
//// With retorna un nuevo logger con campos adicionales
////
//// func (l *zapLogger) With(fields ...Field) Logger {
//// return &zapLogger{
//// logger: l.logger.With(l.convertFields(fields)...),
//// }
//// }
//func (l *zapLogger) With(fields ...Field) Logger {
// return &zapLogger{
// logger: l.logger.With(l.convertFields(fields)...),
// }
//}
//
////package logger
////
////import (
//// "api-soap-facturacion/internal/config"
//// "go.uber.org/zap"
//// "go.uber.org/zap/zapcore"
////)
////
////// Logger es una interfaz que define los métodos necesarios para el logging
////type Logger interface {
//// Debug(msg string, fields ...Field)
//// Info(msg string, fields ...Field)
//// Warn(msg string, fields ...Field)
//// Error(msg string, fields ...Field)
//// Fatal(msg string, fields ...Field)
//// With(fields ...Field) Logger
////}
////
////// Field es un campo para agregar contexto a los logs
////type Field struct {
//// Key string
//// Value interface{}
////}
////
////// zapLogger implementa la interfaz Logger usando zap
////type zapLogger struct {
//// logger *zap.Logger
////}
////
////// NewLogger crea una nueva instancia de Logger
////func NewLogger(cfg *config.Config) Logger {
//// // Configurar nivel de log
//// var level zapcore.Level
//// switch cfg.App.LogLevel {
//// case "debug":
//// level = zapcore.DebugLevel
//// case "info":
//// level = zapcore.InfoLevel
//// case "warn":
//// level = zapcore.WarnLevel
//// case "error":
//// level = zapcore.ErrorLevel
//// default:
//// level = zapcore.InfoLevel
//// }
////
//// // Configuración de zap
//// zapConfig := zap.Config{
//// Level: zap.NewAtomicLevelAt(level),
//// Development: cfg.App.Environment == "development",
//// Encoding: "json",
//// OutputPaths: []string{"stdout"},
//// ErrorOutputPaths: []string{"stderr"},
//// EncoderConfig: zapcore.EncoderConfig{
//// TimeKey: "time",
//// LevelKey: "level",
//// NameKey: "logger",
//// CallerKey: "caller",
//// MessageKey: "msg",
//// StacktraceKey: "stacktrace",
//// LineEnding: zapcore.DefaultLineEnding,
//// EncodeLevel: zapcore.LowercaseLevelEncoder,
//// EncodeTime: zapcore.ISO8601TimeEncoder,
//// EncodeDuration: zapcore.SecondsDurationEncoder,
//// EncodeCaller: zapcore.ShortCallerEncoder,
//// },
//// }
////
//// // Agregar service name a todos los logs
//// logger, _ := zapConfig.Build()
//// logger = logger.With(zap.String("service", cfg.App.Name))
////
//// return &zapLogger{logger: logger}
////}
////
////// NewField crea un campo para agregar a los logs
////func NewField(key string, value interface{}) Field {
//// return Field{Key: key, Value: value}
////}
////
////// convertFields convierte nuestros campos a campos de zap
////func (l *zapLogger) convertFields(fields []Field) []zap.Field {
//// zapFields := make([]zap.Field, len(fields))
//// for i, field := range fields {
//// zapFields[i] = zap.Any(field.Key, field.Value)
//// }
//// return zapFields
////}
////
////// Debug registra un mensaje con nivel Debug
////func (l *zapLogger) Debug(msg string, fields ...Field) {
//// l.logger.Debug(msg, l.convertFields(fields)...)
////}
////
////// Info registra un mensaje con nivel Info
////func (l *zapLogger) Info(msg string, fields ...Field) {
//// l.logger.Info(msg, l.convertFields(fields)...)
////}
////
////// Warn registra un mensaje con nivel Warn
////func (l *zapLogger) Warn(msg string, fields ...Field) {
//// l.logger.Warn(msg, l.convertFields(fields)...)
////}
////
////// Error registra un mensaje con nivel Error
////func (l *zapLogger) Error(msg string, fields ...Field) {
//// l.logger.Error(msg, l.convertFields(fields)...)
////}
////
////// Fatal registra un mensaje con nivel Fatal
////func (l *zapLogger) Fatal(msg string, fields ...Field) {
//// l.logger.Fatal(msg, l.convertFields(fields)...)
////}
////
////// With retorna un nuevo logger con campos adicionales
////func (l *zapLogger) With(fields ...Field) Logger {
//// return &zapLogger{
//// logger: l.logger.With(l.convertFields(fields)...),
//// }
////}