package config import ( "encoding/json" "fmt" "log" "os" "reflect" "strings" "time" amqp "github.com/rabbitmq/amqp091-go" "github.com/sirupsen/logrus" "github.com/spf13/viper" "gorm.io/gorm/logger" ) const ( LOG_DIR = "./config/logs/query_log" ) type GroupedFieldsFormatter struct { TimestampFormat string } func SetupLogger() logger.Interface { err := os.MkdirAll(LOG_DIR, os.ModePerm) if err != nil { log.Fatalf("failed to create log directory: %v", err) } currentMonth := time.Now().Format("January") currentMonth = strings.ToLower(currentMonth) logFileName := currentMonth + "_query.log" logFile, err := os.OpenFile(LOG_DIR+"/"+logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { log.Fatalf("failed to open log file: %v", err) } newLogger := logger.New( log.New(logFile, "\r\n", log.LstdFlags), logger.Config{ SlowThreshold: time.Second, LogLevel: logger.Info, Colorful: false, }, ) return newLogger } func SetupLoggerLogrus() *logrus.Logger { err := os.MkdirAll(LOG_DIR, os.ModePerm) if err != nil { log.Fatalf("failed to create log directory: %v", err) } currentMonth := time.Now().Format("January") currentMonth = strings.ToLower(currentMonth) logFileName := currentMonth + "_app.log" // atau "_logrus.log" sesuai kebutuhan logFile, err := os.OpenFile(LOG_DIR+"/"+logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { log.Fatalf("failed to open log file: %v", err) } logger := logrus.New() logger.SetFormatter(&GroupedFieldsFormatter{TimestampFormat: time.RFC3339}) logger.SetOutput(logFile) // ✔️ Pasang RabbitMQ Hook kalau channel berhasil connect if RabbitChannel != nil { queue := viper.GetString("RABBITMQ_LOG_QUEUE") // contoh: log_queue hook := NewRabbitMQHook(RabbitChannel, queue) logger.AddHook(hook) } logLevel := strings.ToLower(viper.GetString("LOG_LEVEL")) if logLevel == "production" { logger.SetLevel(logrus.WarnLevel) } else { logger.SetLevel(logrus.TraceLevel) } return logger } func (f *GroupedFieldsFormatter) Format(entry *logrus.Entry) ([]byte, error) { data := make(map[string]interface{}) fields := make(map[string]interface{}) // Pisahkan field bawaan dan custom for k, v := range entry.Data { fields[k] = v } data["fields"] = fields data["level"] = entry.Level.String() data["msg"] = entry.Message data["time"] = entry.Time.Format(f.TimestampFormat) serialized, err := json.Marshal(sanitizeJSON(data)) if err != nil { return nil, err } return append(serialized, '\n'), nil } func sanitizeJSON(v interface{}) interface{} { switch val := v.(type) { // kalau error → convert ke string case error: return val.Error() // kalau stringer → gunakan String() case fmt.Stringer: return val.String() } // kalau map → iterate dan sanitize value di dalamnya if m, ok := v.(map[string]interface{}); ok { safeMap := make(map[string]interface{}) for k, val := range m { safeMap[k] = sanitizeJSON(val) } return safeMap } // func tidak boleh diserialize if reflect.TypeOf(v).Kind() == reflect.Func { return "" } // struct kompleks → fallback string agar tidak error _, err := json.Marshal(v) if err != nil { return fmt.Sprintf("%+v", v) } return v } // 🔹 Arahkan log bawaan Go ke logrus (biar library lain ikut nulis ke file) func RedirectDefaultLogger(logger *logrus.Logger) { log.SetOutput(logger.Writer()) } type RabbitMQHook struct { Channel *amqp.Channel Queue string } func NewRabbitMQHook(ch *amqp.Channel, queue string) *RabbitMQHook { return &RabbitMQHook{ Channel: ch, Queue: queue, } } func (h *RabbitMQHook) Levels() []logrus.Level { return logrus.AllLevels } func (h *RabbitMQHook) Fire(entry *logrus.Entry) error { msg := map[string]interface{}{ "level": entry.Level.String(), "msg": entry.Message, "time": entry.Time.Format(time.RFC3339), "data": entry.Data, } body, err := json.Marshal(msg) if err != nil { return err } return h.Channel.Publish( "", h.Queue, false, false, amqp.Publishing{ ContentType: "application/json", Body: body, }, ) } // type LogMessage struct { // Level string `json:"level"` // Message string `json:"message"` // Time time.Time `json:"time"` // } // func SendLog(level, message string) { // if RabbitChannel == nil { // return // } // logData := LogMessage{ // Level: level, // Message: message, // Time: time.Now(), // } // body, _ := json.Marshal(logData) // RabbitChannel.Publish( // "", // default exchange // "app_logs", // queue name // false, // false, // amqp.Publishing{ // ContentType: "application/json", // Body: body, // }, // ) // }