package logger import ( "encoding/json" "io" "log" "os" "strings" "time" ) // Fields adalah data tambahan yang disisipkan ke satu baris log, contoh: // // logger.Info("request selesai", logger.Fields{"path": "/users", "status": 200}) type Fields map[string]interface{} var std = log.New(os.Stdout, "", 0) // SetOutput mengganti tujuan tulis log. Berguna untuk unit test (redirect ke buffer) // atau kalau nanti mau kirim log ke file/agent lain. func SetOutput(w io.Writer) { std = log.New(w, "", 0) } var sensitiveKeys = map[string]bool{ "password": true, "password_confirmation": true, "old_password": true, "new_password": true, "confirm_password": true, "token": true, "access_token": true, "refresh_token": true, "authorization": true, "secret": true, "jwt_secret": true, "credit_card": true, "card_number": true, "cvv": true, "otp": true, } const redactedValue = "***REDACTED***" // sanitize menyensor field sensitif secara rekursif (termasuk di dalam nested object/body JSON) // supaya data seperti password tidak pernah ikut tercetak di log, walau caller lupa menyaringnya. func sanitize(fields Fields) Fields { if fields == nil { return nil } clean := make(Fields, len(fields)) for key, value := range fields { if sensitiveKeys[strings.ToLower(key)] { clean[key] = redactedValue continue } switch v := value.(type) { case map[string]interface{}: clean[key] = sanitize(Fields(v)) case Fields: clean[key] = sanitize(v) default: clean[key] = value } } return clean } type entry struct { Time string `json:"time"` Level string `json:"level"` Message string `json:"message"` Fields Fields `json:"fields,omitempty"` } func write(level, msg string, fields Fields) { e := entry{ Time: time.Now().Format(time.RFC3339), Level: level, Message: msg, Fields: sanitize(fields), } b, err := json.Marshal(e) if err != nil { // fallback kalau ternyata ada value yang gagal di-marshal, tetap harus ada jejak log-nya std.Printf("[%s] %s (gagal marshal fields: %v)", level, msg, err) return } std.Println(string(b)) } // Info dipakai untuk mencatat aktivitas normal, contoh: request masuk dan sukses diproses. func Info(msg string, fields Fields) { write("INFO", msg, fields) } // Warn dipakai untuk kondisi yang bukan bug tapi perlu diperhatikan, contoh: request 4xx // (input salah, tidak diotorisasi, dst). func Warn(msg string, fields Fields) { write("WARN", msg, fields) } // Error dipakai untuk kegagalan sisi server (5xx) atau error tak terduga lainnya yang // butuh investigasi lebih lanjut oleh developer. func Error(msg string, fields Fields) { write("ERROR", msg, fields) }