Repository: casbin/casbin
Pull request #631 (2020-10-27 12:25:38)

feat: Rename role
#613 

00LT00: plz review @hsluoyz @nodece @GopherJ 

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8762972..a593d26 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,7 +6,7 @@ This project adheres to the [Contributor Covenant 1.2.](https://www.contributor-
 
 ## Questions
 
-* We do our best to have un [up to date documentation](https://casbin.org/docs/en/overview)
+* We do our best to have an [up-to-date documentation](https://casbin.org/docs/en/overview)
 * [Stack Overflow](https://stackoverflow.com) is the best place to start if you have a question. Please use the [casbin tag](https://stackoverflow.com/tags/casbin/info) we are actively monitoring. We encourage you to use Stack Overflow specially for Modeling Access Control Problems, in order to build a shared knowledge base.
 * You can also join our [Gitter community](https://gitter.im/casbin/Lobby).
 
diff --git a/enforcer.go b/enforcer.go
index ab0aec8..ef77d32 100644
--- a/enforcer.go
+++ b/enforcer.go
@@ -17,8 +17,6 @@ package casbin
 import (
 	"errors"
 	"fmt"
-	"strings"
-
 	"github.com/Knetic/govaluate"
 	"github.com/casbin/casbin/v2/effect"
 	"github.com/casbin/casbin/v2/log"
@@ -37,14 +35,18 @@ type Enforcer struct {
 	fm        model.FunctionMap
 	eft       effect.Effector
 
-	adapter persist.Adapter
-	watcher persist.Watcher
-	rm      rbac.RoleManager
+	adapter    persist.Adapter
+	watcher    persist.Watcher
+	dispatcher persist.Dispatcher
+	rm         rbac.RoleManager
+
+	enabled              bool
+	autoSave             bool
+	autoBuildRoleLinks   bool
+	autoNotifyWatcher    bool
+	autoNotifyDispatcher bool
 
-	enabled            bool
-	autoSave           bool
-	autoBuildRoleLinks bool
-	autoNotifyWatcher  bool
+	logger log.Logger
 }
 
 // NewEnforcer creates an enforcer via file or DB.
@@ -59,7 +61,7 @@ type Enforcer struct {
 // 	e := casbin.NewEnforcer("path/to/basic_model.conf", a)
 //
 func NewEnforcer(params ...interface{}) (*Enforcer, error) {
-	e := &Enforcer{}
+	e := &Enforcer{logger: &log.DefaultLogger{}}
 
 	parsedParamLen := 0
 	paramLen := len(params)
@@ -67,7 +69,14 @@ func NewEnforcer(params ...interface{}) (*Enforcer, error) {
 		enableLog, ok := params[paramLen-1].(bool)
 		if ok {
 			e.EnableLog(enableLog)
+			parsedParamLen++
+		}
+	}
 
+	if paramLen-parsedParamLen >= 1 {
+		logger, ok := params[paramLen-parsedParamLen-1].(log.Logger)
+		if ok {
+			e.logger = logger
 			parsedParamLen++
 		}
 	}
@@ -147,6 +156,7 @@ func (e *Enforcer) InitWithModelAndAdapter(m model.Model, adapter persist.Adapte
 	e.adapter = adapter
 
 	e.model = m
+	m.SetLogger(e.logger)
 	e.model.PrintModel()
 	e.fm = model.LoadFunctionMap()
 
@@ -164,6 +174,13 @@ func (e *Enforcer) InitWithModelAndAdapter(m model.Model, adapter persist.Adapte
 	return nil
 }
 
+// SetLogger changes the current enforcer's logger.
+func (e *Enforcer) SetLogger(logger log.Logger) {
+	e.logger = logger
+	e.model.SetLogger(e.logger)
+	e.rm.SetLogger(e.logger)
+}
+
 func (e *Enforcer) initialize() {
 	e.rm = defaultrolemanager.NewRoleManager(10)
 	e.eft = effect.NewDefaultEffector()
@@ -173,6 +190,8 @@ func (e *Enforcer) initialize() {
 	e.autoSave = true
 	e.autoBuildRoleLinks = true
 	e.autoNotifyWatcher = true
+
+	e.rm.SetLogger(e.logger)
 }
 
 // LoadModel reloads the model from the model CONF file.
@@ -183,6 +202,7 @@ func (e *Enforcer) LoadModel() error {
 	if err != nil {
 		return err
 	}
+	e.model.SetLogger(e.logger)
 
 	e.model.PrintModel()
 	e.fm = model.LoadFunctionMap()
@@ -202,6 +222,7 @@ func (e *Enforcer) SetModel(m model.Model) {
 	e.model = m
 	e.fm = model.LoadFunctionMap()
 
+	e.model.SetLogger(e.logger)
 	e.initialize()
 }
 
@@ -221,6 +242,11 @@ func (e *Enforcer) SetWatcher(watcher persist.Watcher) error {
 	return watcher.SetUpdateCallback(func(string) { _ = e.LoadPolicy() })
 }
 
+// SetDispatcher sets the current dispatcher.
+func (e *Enforcer) SetDispatcher(dispatcher persist.Dispatcher) {
+	e.dispatcher = dispatcher
+}
+
 // GetRoleManager gets the current role manager.
 func (e *Enforcer) GetRoleManager() rbac.RoleManager {
 	return e.rm
@@ -329,7 +355,12 @@ func (e *Enforcer) EnableEnforce(enable bool) {
 
 // EnableLog changes whether Casbin will log messages to the Logger.
 func (e *Enforcer) EnableLog(enable bool) {
-	log.GetLogger().EnableLog(enable)
+	e.logger.EnableLog(enable)
+}
+
+// IsLogEnabled returns the current logger's enabled status.
+func (e *Enforcer) IsLogEnabled() bool {
+	return e.logger.IsEnabled()
 }
 
 // EnableAutoNotifyWatcher controls whether to save a policy rule automatically notify the Watcher when it is added or removed.
@@ -337,6 +368,11 @@ func (e *Enforcer) EnableAutoNotifyWatcher(enable bool) {
 	e.autoNotifyWatcher = enable
 }
 
+// EnableAutoNotifyDispatcher controls whether to save a policy rule automatically notify the Dispatcher when it is added or removed.
+func (e *Enforcer) EnableAutoNotifyDispatcher(enable bool) {
+	e.autoNotifyDispatcher = enable
+}
+
 // EnableAutoSave controls whether to save a policy rule automatically to the adapter when it is added or removed.
 func (e *Enforcer) EnableAutoSave(autoSave bool) {
 	e.autoSave = autoSave
@@ -510,7 +546,6 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 		parameters.pVals = make([]string, len(parameters.pTokens))
 
 		result, err := expression.Eval(parameters)
-		// log.LogPrint("Result: ", result)
 
 		if err != nil {
 			return false, err
@@ -523,51 +558,21 @@ func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interfac
 		}
 	}
 
-	// log.LogPrint("Rule Results: ", policyEffects)
-
 	result, explainIndex, err := e.eft.MergeEffects(e.model["e"]["e"].Value, policyEffects, matcherResults)
 	if err != nil {
 		return false, err
 	}
 
+	var logExplains [][]string
+
 	if explains != nil {
+		logExplains = append(logExplains, *explains)
 		if explainIndex != -1 && len(e.model["p"]["p"].Policy) > explainIndex {
 			*explains = e.model["p"]["p"].Policy[explainIndex]
 		}
 	}
 
-	// Log request.
-	if log.GetLogger().IsEnabled() {
-		var reqStr strings.Builder
-		reqStr.WriteString("Request: ")
-		for i, rval := range rvals {
-			if i != len(rvals)-1 {
-				reqStr.WriteString(fmt.Sprintf("%v, ", rval))
-			} else {
-				reqStr.WriteString(fmt.Sprintf("%v", rval))
-			}
-		}
-		reqStr.WriteString(fmt.Sprintf(" ---> %t\n", result))
-
-		if explains != nil {
-			reqStr.WriteString("Hit Policy: ")
-			for i, pval := range *explains {
-				if i != len(*explains)-1 {
-					reqStr.WriteString(fmt.Sprintf("%v, ", pval))
-				} else {
-					reqStr.WriteString(fmt.Sprintf("%v \n", pval))
-				}
-			}
-
-		}
-
-		logType := log.LogTypeGrantedAccessRequest
-		if !result {
-			logType = log.LogTypeRejectedAccessRequest
-		}
-
-		log.LogEnforce(logType, reqStr.String(), &rvals, explains, &[]interface{}{result})
-	}
+	e.logger.LogEnforce(expString, rvals, result, logExplains)
 
 	return result, nil
 }
diff --git a/enforcer_synced.go b/enforcer_synced.go
index c61d693..4b75de8 100644
--- a/enforcer_synced.go
+++ b/enforcer_synced.go
@@ -20,7 +20,6 @@ import (
 	"time"
 
 	"github.com/Knetic/govaluate"
-	"github.com/casbin/casbin/v2/log"
 	"github.com/casbin/casbin/v2/persist"
 )
 
@@ -65,8 +64,6 @@ func (e *SyncedEnforcer) StartAutoLoadPolicy(d time.Duration) {
 			atomic.StoreInt32(&(e.autoLoadRunning), int32(0))
 		}()
 		n := 1
-		//log.LogPrintf("Start automatically load policy")
-		log.LogPolicy(log.LogTypeLoadPolicy, "Start automatically load policy", nil, nil, nil, nil)
 		for {
 			select {
 			case <-ticker.C:
@@ -76,8 +73,6 @@ func (e *SyncedEnforcer) StartAutoLoadPolicy(d time.Duration) {
 				// log.Print("Load policy for time: ", n)
 				n++
 			case <-e.stopAutoLoad:
-				//log.LogPrintf("Stop automatically load policy")
-				log.LogPolicy(log.LogTypeLoadPolicy, "Stop automatically load policy", nil, nil, nil, nil)
 				return
 			}
 		}
diff --git a/go.mod b/go.mod
index c6c765a..62fe536 100644
--- a/go.mod
+++ b/go.mod
@@ -1,5 +1,8 @@
 module github.com/casbin/casbin/v2
 
-require github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible
+require (
+	github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible
+	github.com/golang/mock v1.4.4
+)
 
 go 1.13
diff --git a/go.sum b/go.sum
index d1c7c35..8563487 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,10 @@
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
diff --git a/internal_api.go b/internal_api.go
index cc453c5..303c5e9 100644
--- a/internal_api.go
+++ b/internal_api.go
@@ -30,6 +30,10 @@ func (e *Enforcer) shouldPersist() bool {
 
 // addPolicy adds a rule to the current policy.
 func (e *Enforcer) addPolicy(sec string, ptype string, rule []string) (bool, error) {
+	if e.dispatcher != nil && e.autoNotifyDispatcher {
+		return true, e.dispatcher.AddPolicies(sec, ptype, [][]string{rule})
+	}
+
 	if e.model.HasPolicy(sec, ptype, rule) {
 		return false, nil
 	}
@@ -66,6 +70,10 @@ func (e *Enforcer) addPolicy(sec string, ptype string, rule []string) (bool, err
 
 // addPolicies adds rules to the current policy.
 func (e *Enforcer) addPolicies(sec string, ptype string, rules [][]string) (bool, error) {
+	if e.dispatcher != nil && e.autoNotifyDispatcher {
+		return true, e.dispatcher.AddPolicies(sec, ptype, rules)
+	}
+
 	if e.model.HasPolicies(sec, ptype, rules) {
 		return false, nil
 	}
@@ -99,6 +107,10 @@ func (e *Enforcer) addPolicies(sec string, ptype string, rules [][]string) (bool
 
 // removePolicy removes a rule from the current policy.
 func (e *Enforcer) removePolicy(sec string, ptype string, rule []string) (bool, error) {
+	if e.dispatcher != nil && e.autoNotifyDispatcher {
+		return true, e.dispatcher.RemovePolicies(sec, ptype, [][]string{rule})
+	}
+
 	if e.shouldPersist() {
 		if err := e.adapter.RemovePolicy(sec, ptype, rule); err != nil {
 			if err.Error() != notImplemented {
@@ -134,6 +146,10 @@ func (e *Enforcer) removePolicy(sec string, ptype string, rule []string) (bool,
 }
 
 func (e *Enforcer) updatePolicy(sec string, ptype string, oldRule []string, newRule []string) (bool, error) {
+	if e.dispatcher != nil && e.autoNotifyDispatcher {
+		return true, e.dispatcher.UpdatePolicy(sec, ptype, oldRule, newRule)
+	}
+
 	if e.shouldPersist() {
 		if err := e.adapter.(persist.UpdatableAdapter).UpdatePolicy(sec, ptype, oldRule, newRule); err != nil {
 			if err.Error() != notImplemented {
@@ -176,6 +192,10 @@ func (e *Enforcer) removePolicies(sec string, ptype string, rules [][]string) (b
 		return false, nil
 	}
 
+	if e.dispatcher != nil && e.autoNotifyDispatcher {
+		return true, e.dispatcher.RemovePolicies(sec, ptype, rules)
+	}
+
 	if e.shouldPersist() {
 		if err := e.adapter.(persist.BatchAdapter).RemovePolicies(sec, ptype, rules); err != nil {
 			if err.Error() != notImplemented {
@@ -212,6 +232,10 @@ func (e *Enforcer) removeFilteredPolicy(sec string, ptype string, fieldIndex int
 		return false, Err.INVALID_FIELDVAULES_PARAMETER
 	}
 
+	if e.dispatcher != nil && e.autoNotifyDispatcher {
+		return true, e.dispatcher.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
+	}
+
 	if e.shouldPersist() {
 		if err := e.adapter.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...); err != nil {
 			if err.Error() != notImplemented {
diff --git a/log/default_logger.go b/log/default_logger.go
index 6950907..650704c 100644
--- a/log/default_logger.go
+++ b/log/default_logger.go
@@ -15,59 +15,83 @@
 package log
 
 import (
+	"fmt"
 	"log"
-	"sync/atomic"
+	"strings"
 )
 
 // DefaultLogger is the implementation for a Logger using golang log.
 type DefaultLogger struct {
-	enable int32
+	enabled bool
 }
 
 func (l *DefaultLogger) EnableLog(enable bool) {
-	i := 0
-	if enable {
-		i = 1
-	}
-	atomic.StoreInt32(&(l.enable), int32(i))
+	l.enabled = enable
 }
 
 func (l *DefaultLogger) IsEnabled() bool {
-	return atomic.LoadInt32(&(l.enable)) != 0
+	return l.enabled
 }
 
-func (l *DefaultLogger) LogModel(event int, line []string, model [][]string) {
-	for _, v := range line {
-		log.Print(v)
+func (l *DefaultLogger) LogModel(model [][]string) {
+	if !l.enabled {
+		return
+	}
+	var str strings.Builder
+	str.WriteString("Model: ")
+	for _, v := range model {
+		str.WriteString(fmt.Sprintf("%v\n", v))
 	}
-}
 
-func (l *DefaultLogger) LogEnforce(event int, line string, request *[]interface{}, policies *[]string, result *[]interface{}) {
-	log.Print(line)
+	log.Println(str.String())
 }
 
-func (l *DefaultLogger) LogPolicy(event int, line string, pPolicyFormat []string, gPolicyFormat []string, pPolicy *[]interface{}, gPolicy *[]interface{}) {
-	log.Print(line)
-	if pPolicy != nil {
-		for k, v := range *pPolicy {
-			log.Print("p: ", pPolicyFormat[k], ": ", v)
+func (l *DefaultLogger) LogEnforce(matcher string, request []interface{}, result bool, explains [][]string) {
+	if !l.enabled {
+		return
+	}
+
+	var reqStr strings.Builder
+	reqStr.WriteString("Request: ")
+	for i, rval := range request {
+		if i != len(request)-1 {
+			reqStr.WriteString(fmt.Sprintf("%v, ", rval))
+		} else {
+			reqStr.WriteString(fmt.Sprintf("%v", rval))
 		}
 	}
-	if gPolicy != nil {
-		for k, v := range *gPolicy {
-			log.Print("g: ", gPolicyFormat[k], ": ", v)
+	reqStr.WriteString(fmt.Sprintf(" ---> %t\n", result))
+
+	reqStr.WriteString("Hit Policy: ")
+	for i, pval := range explains {
+		if i != len(explains)-1 {
+			reqStr.WriteString(fmt.Sprintf("%v, ", pval))
+		} else {
+			reqStr.WriteString(fmt.Sprintf("%v \n", pval))
 		}
 	}
+
+	log.Println(reqStr.String())
 }
 
-func (l *DefaultLogger) LogRole(event int, line string, role []string) {
-	log.Print(line)
+func (l *DefaultLogger) LogPolicy(policy map[string][][]string) {
+	if !l.enabled {
+		return
+	}
+
+	var str strings.Builder
+	str.WriteString("Policy: ")
+	for k, v := range policy {
+		str.WriteString(fmt.Sprintf("%s : %v\n", k, v))
+	}
+
+	log.Println(str.String())
 }
 
-/*
-func (l *DefaultLogger) Printf(format string, v ...interface{}) {
-	if l.IsEnabled() {
-		log.Printf(format, v...)
+func (l *DefaultLogger) LogRole(roles []string) {
+	if !l.enabled {
+		return
 	}
+
+	log.Println("Roles: ", roles)
 }
-*/
diff --git a/log/log_util.go b/log/log_util.go
index f789ae6..191c817 100644
--- a/log/log_util.go
+++ b/log/log_util.go
@@ -26,34 +26,22 @@ func GetLogger() Logger {
 	return logger
 }
 
-/*
-// LogPrint prints the log.
-func LogPrint(v ...interface{}) {
-	logger.Print(v...)
-}
-
-// LogPrintf prints the log with the format.
-func LogPrintf(format string, v ...interface{}) {
-	logger.Printf(format, v...)
-}
-*/
-
 // LogModel logs the model information.
-func LogModel(event int, line []string, model [][]string) {
-	logger.LogModel(event, line, model)
+func LogModel(model [][]string) {
+	logger.LogModel(model)
 }
 
 // LogEnforce logs the enforcer information.
-func LogEnforce(event int, line string, request *[]interface{}, policies *[]string, result *[]interface{}) {
-	logger.LogEnforce(event, line, request, policies, result)
+func LogEnforce(matcher string, request []interface{}, result bool, explains [][]string) {
+	logger.LogEnforce(matcher, request, result, explains)
 }
 
-// LogRole logs the role information.
-func LogRole(event int, line string, role []string) {
-	logger.LogRole(event, line, role)
+// LogRole log info related to role.
+func LogRole(roles []string) {
+	logger.LogRole(roles)
 }
 
 // LogPolicy logs the policy information.
-func LogPolicy(event int, line string, pPolicyFormat []string, gPolicyFormat []string, pPolicy *[]interface{}, gPolicy *[]interface{}) {
-	logger.LogPolicy(event, line, pPolicyFormat, gPolicyFormat, pPolicy, gPolicy)
+func LogPolicy(policy map[string][][]string) {
+	logger.LogPolicy(policy)
 }
diff --git a/log/logger.go b/log/logger.go
index 6f7c40e..c60e07a 100644
--- a/log/logger.go
+++ b/log/logger.go
@@ -14,39 +14,25 @@
 
 package log
 
-const (
-	LogTypeGrantedAccessRequest = iota
-	LogTypeRejectedAccessRequest
-	LogTypeLoadPolicy
-	LogTypePrintModel
-	LogTypePrintPolicy
-	LogTypePrintRole
-	LogTypeLinkRole
-)
+//go:generate mockgen -destination=./mocks/mock_logger.go -package=mocks github.com/casbin/casbin/v2/log Logger
 
 // Logger is the logging interface implementation.
 type Logger interface {
-	//EnableLog controls whether print the message.
+	// EnableLog controls whether print the message.
 	EnableLog(bool)
 
-	//IsEnabled returns if logger is enabled.
+	// IsEnabled returns if logger is enabled.
 	IsEnabled() bool
 
-	//Print formats using the default formats for its operands and logs the message.
-	//Print(...interface{})
-
-	//Printf formats according to a format specifier and logs the message.
-	//Printf(string, ...interface{})
-
 	// LogModel log info related to model.
-	LogModel(event int, line []string, model [][]string)
+	LogModel(model [][]string)
 
 	// LogEnforce log info related to enforce.
-	LogEnforce(event int, line string, request *[]interface{}, policies *[]string, result *[]interface{})
+	LogEnforce(matcher string, request []interface{}, result bool, explains [][]string)
 
 	// LogRole log info related to role.
-	LogRole(event int, line string, role []string)
+	LogRole(roles []string)
 
 	// LogPolicy log info related to policy.
-	LogPolicy(event int, line string, pPolicyFormat []string, gPolicyFormat []string, pPolicy *[]interface{}, gPolicy *[]interface{})
+	LogPolicy(policy map[string][][]string)
 }
diff --git a/log/mocks/mock_logger.go b/log/mocks/mock_logger.go
new file mode 100644
index 0000000..49d5347
--- /dev/null
+++ b/log/mocks/mock_logger.go
@@ -0,0 +1,107 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/casbin/casbin/v2/log (interfaces: Logger)
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+	gomock "github.com/golang/mock/gomock"
+	reflect "reflect"
+)
+
+// MockLogger is a mock of Logger interface
+type MockLogger struct {
+	ctrl     *gomock.Controller
+	recorder *MockLoggerMockRecorder
+}
+
+// MockLoggerMockRecorder is the mock recorder for MockLogger
+type MockLoggerMockRecorder struct {
+	mock *MockLogger
+}
+
+// NewMockLogger creates a new mock instance
+func NewMockLogger(ctrl *gomock.Controller) *MockLogger {
+	mock := &MockLogger{ctrl: ctrl}
+	mock.recorder = &MockLoggerMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
+	return m.recorder
+}
+
+// EnableLog mocks base method
+func (m *MockLogger) EnableLog(arg0 bool) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "EnableLog", arg0)
+}
+
+// EnableLog indicates an expected call of EnableLog
+func (mr *MockLoggerMockRecorder) EnableLog(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableLog", reflect.TypeOf((*MockLogger)(nil).EnableLog), arg0)
+}
+
+// IsEnabled mocks base method
+func (m *MockLogger) IsEnabled() bool {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsEnabled")
+	ret0, _ := ret[0].(bool)
+	return ret0
+}
+
+// IsEnabled indicates an expected call of IsEnabled
+func (mr *MockLoggerMockRecorder) IsEnabled() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsEnabled", reflect.TypeOf((*MockLogger)(nil).IsEnabled))
+}
+
+// LogEnforce mocks base method
+func (m *MockLogger) LogEnforce(arg0 string, arg1 []interface{}, arg2 bool, arg3 [][]string) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "LogEnforce", arg0, arg1, arg2, arg3)
+}
+
+// LogEnforce indicates an expected call of LogEnforce
+func (mr *MockLoggerMockRecorder) LogEnforce(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogEnforce", reflect.TypeOf((*MockLogger)(nil).LogEnforce), arg0, arg1, arg2, arg3)
+}
+
+// LogModel mocks base method
+func (m *MockLogger) LogModel(arg0 [][]string) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "LogModel", arg0)
+}
+
+// LogModel indicates an expected call of LogModel
+func (mr *MockLoggerMockRecorder) LogModel(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogModel", reflect.TypeOf((*MockLogger)(nil).LogModel), arg0)
+}
+
+// LogPolicy mocks base method
+func (m *MockLogger) LogPolicy(arg0 map[string][][]string) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "LogPolicy", arg0)
+}
+
+// LogPolicy indicates an expected call of LogPolicy
+func (mr *MockLoggerMockRecorder) LogPolicy(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogPolicy", reflect.TypeOf((*MockLogger)(nil).LogPolicy), arg0)
+}
+
+// LogRole mocks base method
+func (m *MockLogger) LogRole(arg0 []string) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "LogRole", arg0)
+}
+
+// LogRole indicates an expected call of LogRole
+func (mr *MockLoggerMockRecorder) LogRole(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogRole", reflect.TypeOf((*MockLogger)(nil).LogRole), arg0)
+}
diff --git a/management_api.go b/management_api.go
index aa16123..fbd9207 100644
--- a/management_api.go
+++ b/management_api.go
@@ -296,6 +296,14 @@ func (e *Enforcer) RemoveNamedGroupingPolicies(ptype string, rules [][]string) (
 	return e.removePolicies("g", ptype, rules)
 }
 
+func (e *Enforcer) UpdateGroupingPolicy(oldRule []string, newRule []string) (bool, error) {
+	return e.UpdateNamedGroupingPolicy("g", oldRule, newRule)
+}
+
+func (e *Enforcer) UpdateNamedGroupingPolicy(ptype string, oldRule []string, newRule []string) (bool, error) {
+	return e.updatePolicy("g", ptype, oldRule, newRule)
+}
+
 // RemoveFilteredNamedGroupingPolicy removes a role inheritance rule from the current named policy, field filters can be specified.
 func (e *Enforcer) RemoveFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) {
 	return e.removeFilteredPolicy("g", ptype, fieldIndex, fieldValues...)
diff --git a/model/assertion.go b/model/assertion.go
index 7ea850f..5ff4ca4 100644
--- a/model/assertion.go
+++ b/model/assertion.go
@@ -31,6 +31,8 @@ type Assertion struct {
 	Policy    [][]string
 	PolicyMap map[string]int
 	RM        rbac.RoleManager
+
+	logger log.Logger
 }
 
 func (ast *Assertion) buildIncrementalRoleLinks(rm rbac.RoleManager, op PolicyOp, rules [][]string) error {
@@ -83,8 +85,9 @@ func (ast *Assertion) buildRoleLinks(rm rbac.RoleManager) error {
 		}
 	}
 
-	//log.LogPrint("Role links for: " + ast.Key)
-	log.LogRole(log.LogTypeLinkRole, "Role links for: " + ast.Key, []string{ast.Key})
+	return nil
+}
 
-	return ast.RM.PrintRoles()
+func (ast *Assertion) setLogger(logger log.Logger) {
+	ast.logger = logger
 }
diff --git a/model/model.go b/model/model.go
index 4c7bd00..c297f48 100644
--- a/model/model.go
+++ b/model/model.go
@@ -56,6 +56,7 @@ func (model Model) AddDef(sec string, key string, value string) bool {
 	ast.Key = key
 	ast.Value = value
 	ast.PolicyMap = make(map[string]int)
+	ast.setLogger(model.GetLogger())
 
 	if sec == "r" || sec == "p" {
 		ast.Tokens = strings.Split(ast.Value, ",")
@@ -94,9 +95,26 @@ func loadSection(model Model, cfg config.ConfigInterface, sec string) {
 	}
 }
 
+// SetLogger sets the model's logger.
+func (model Model) SetLogger(logger log.Logger) {
+	for _, astMap := range model {
+		for _, ast := range astMap {
+			ast.logger = logger
+		}
+	}
+	model["logger"] = AssertionMap{"logger": &Assertion{logger: logger}}
+}
+
+// GetLogger returns the model's logger.
+func (model Model) GetLogger() log.Logger {
+	return model["logger"]["logger"].logger
+}
+
 // NewModel creates an empty model.
 func NewModel() Model {
 	m := make(Model)
+	m.SetLogger(&log.DefaultLogger{})
+
 	return m
 }
 
@@ -167,16 +185,20 @@ func (model Model) hasSection(sec string) bool {
 
 // PrintModel prints the model to the log.
 func (model Model) PrintModel() {
-	//log.LogPrint("Model:")
-	logInfo := []string{"Model"}
+	if !model.GetLogger().IsEnabled() {
+		return
+	}
+
 	var modelInfo [][]string
 	for k, v := range model {
+		if k == "logger" {
+			continue
+		}
+
 		for i, j := range v {
 			modelInfo = append(modelInfo, []string{k, i, j.Value})
-			logInfo = append(logInfo, fmt.Sprintf("%s.%s: %s", k, i, j.Value))
-			//log.LogPrintf("%s.%s: %s", k, i, j.Value)
 		}
 	}
 
-	log.LogModel(log.LogTypePrintModel, logInfo, modelInfo)
+	model.GetLogger().LogModel(modelInfo)
 }
diff --git a/model/policy.go b/model/policy.go
index 69a2fc0..6b70b41 100644
--- a/model/policy.go
+++ b/model/policy.go
@@ -17,7 +17,6 @@ package model
 import (
 	"strings"
 
-	"github.com/casbin/casbin/v2/log"
 	"github.com/casbin/casbin/v2/rbac"
 	"github.com/casbin/casbin/v2/util"
 )
@@ -56,30 +55,33 @@ func (model Model) BuildRoleLinks(rm rbac.RoleManager) error {
 
 // PrintPolicy prints the policy to log.
 func (model Model) PrintPolicy() {
-	if !log.GetLogger().IsEnabled() {
+	if !model.GetLogger().IsEnabled() {
 		return
 	}
-	//log.LogPrint("Policy:")
-	var (
-		pPolicy       []interface{} // or [][][]string
-		gPolicy       []interface{}
-		pPolicyFormat []string
-		gPolicyFormat []string
-	)
 
-	for _, ast := range model["p"] {
-		pPolicyFormat = append(pPolicyFormat, ast.Value)
-		pPolicy = append(pPolicy, ast.Policy)
-		//log.LogPrint(key, ": ", ast.Value, ": ", ast.Policy)
+	policy := make(map[string][][]string)
+
+	for key, ast := range model["p"] {
+		value, found := policy[key]
+		if found {
+			value = append(value, ast.Policy...)
+			policy[key] = value
+		} else {
+			policy[key] = ast.Policy
+		}
 	}
 
-	for _, ast := range model["g"] {
-		gPolicyFormat = append(gPolicyFormat, ast.Value)
-		gPolicy = append(gPolicy, ast.Policy)
-		//log.LogPrint(key, ": ", ast.Value, ": ", ast.Policy)
+	for key, ast := range model["g"] {
+		value, found := policy[key]
+		if found {
+			value = append(value, ast.Policy...)
+			policy[key] = value
+		} else {
+			policy[key] = ast.Policy
+		}
 	}
 
-	log.LogPolicy(log.LogTypePrintPolicy, "Policy: ", pPolicyFormat, gPolicyFormat, &pPolicy, &gPolicy)
+	model.GetLogger().LogPolicy(policy)
 }
 
 // ClearPolicy clears all current policy.
diff --git a/persist/dispatcher.go b/persist/dispatcher.go
new file mode 100644
index 0000000..9aba980
--- /dev/null
+++ b/persist/dispatcher.go
@@ -0,0 +1,29 @@
+// Copyright 2020 The casbin Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package persist
+
+// Dispatcher is the interface for Casbin dispatcher
+type Dispatcher interface {
+	// AddPolicies adds policies rule to all instance.
+	AddPolicies(sec string, ptype string, rules [][]string) error
+	// RemovePolicies removes policies rule from all instance.
+	RemovePolicies(sec string, ptype string, rules [][]string) error
+	// RemoveFilteredPolicy removes policy rules that match the filter from all instance.
+	RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error
+	// ClearPolicy clears all current policy in all instances
+	ClearPolicy() error
+	// UpdatePolicy updates policy rule from all instance.
+	UpdatePolicy(sec string, ptype string, oldRule, newPolicy []string) error
+}
diff --git a/rbac/default-role-manager/role_manager.go b/rbac/default-role-manager/role_manager.go
index c03c542..b46381b 100644
--- a/rbac/default-role-manager/role_manager.go
+++ b/rbac/default-role-manager/role_manager.go
@@ -35,6 +35,8 @@ type RoleManager struct {
 	matchingFunc       MatchingFunc
 	hasDomainPattern   bool
 	domainMatchingFunc MatchingFunc
+
+	logger log.Logger
 }
 
 // NewRoleManager is the constructor for creating an instance of the
@@ -47,6 +49,8 @@ func NewRoleManager(maxHierarchyLevel int) rbac.RoleManager {
 	rm.hasPattern = false
 	rm.hasDomainPattern = false
 
+	rm.SetLogger(&log.DefaultLogger{})
+
 	return &rm
 }
 
@@ -62,6 +66,11 @@ func (rm *RoleManager) AddDomainMatchingFunc(name string, fn MatchingFunc) {
 	rm.domainMatchingFunc = fn
 }
 
+// SetLogger sets role manager's logger.
+func (rm *RoleManager) SetLogger(logger log.Logger) {
+	rm.logger = logger
+}
+
 func (rm *RoleManager) generateTempRoles(domain string) *Roles {
 	rm.allDomains.LoadOrStore(domain, new(Roles))
 
@@ -228,28 +237,23 @@ func (rm *RoleManager) GetUsers(name string, domain ...string) ([]string, error)
 
 // PrintRoles prints all the roles to log.
 func (rm *RoleManager) PrintRoles() error {
-	if log.GetLogger().IsEnabled() {
-		var sb strings.Builder
-		var roles []string
-		rm.allDomains.Range(func(_, value interface{}) bool {
-			value.(*Roles).Range(func(_, value interface{}) bool {
-				if text := value.(*Role).toString(); text != "" {
-					if sb.Len() == 0 {
-						sb.WriteString(text)
-					} else {
-						roles = append(roles, text)
-						sb.WriteString(", ")
-						sb.WriteString(text)
-					}
-				}
-				return true
-			})
+	if !(rm.logger).IsEnabled() {
+		return nil
+	}
 
+	var roles []string
+	rm.allDomains.Range(func(_, value interface{}) bool {
+		value.(*Roles).Range(func(_, value interface{}) bool {
+			if text := value.(*Role).toString(); text != "" {
+				roles = append(roles, text)
+			}
 			return true
 		})
-		//log.LogPrint(sb.String())
-		log.LogRole(log.LogTypePrintRole, sb.String(), roles)
-	}
+
+		return true
+	})
+
+	rm.logger.LogRole(roles)
 	return nil
 }
 
diff --git a/rbac/role_manager.go b/rbac/role_manager.go
index b853bc9..c626cbc 100644
--- a/rbac/role_manager.go
+++ b/rbac/role_manager.go
@@ -14,6 +14,8 @@
 
 package rbac
 
+import "github.com/casbin/casbin/v2/log"
+
 // RoleManager provides interface to define the operations for managing roles.
 type RoleManager interface {
 	// Clear clears all stored data and resets the role manager to the initial state.
@@ -35,4 +37,6 @@ type RoleManager interface {
 	GetUsers(name string, domain ...string) ([]string, error)
 	// PrintRoles prints all the roles to log.
 	PrintRoles() error
+	// SetLogger sets role manager's logger.
+	SetLogger(logger log.Logger)
 }


Test patch:
diff --git a/log/log_util_test.go b/log/log_util_test.go
index e989798..7b72b40 100644
--- a/log/log_util_test.go
+++ b/log/log_util_test.go
@@ -15,54 +15,42 @@
 package log
 
 import (
-	"reflect"
 	"testing"
-)
 
-type LoggerTester struct {
-	event       int
-	line        string
-	lastMessage []interface{}
-}
+	"github.com/casbin/casbin/v2/log/mocks"
 
-func (t *LoggerTester) EnableLog(bool)  {}
-func (t *LoggerTester) IsEnabled() bool { return true }
+	"github.com/golang/mock/gomock"
+)
 
-func (t *LoggerTester) LogModel(event int, line []string, model [][]string) {
-	t.event = event
-	t.line = ""
-	t.lastMessage = []interface{}{model}
-}
+func TestLog(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
 
-func (t *LoggerTester) LogEnforce(event int, line string, request *[]interface{}, policies *[]string, result *[]interface{}) {
-	t.event = event
-	t.line = ""
-	t.lastMessage = *request
-}
+	m := mocks.NewMockLogger(ctrl)
+	SetLogger(m)
 
-func (t *LoggerTester) LogRole(event int, line string, role []string) {
-	t.event = event
-	t.line = ""
-	t.lastMessage = []interface{}{role}
-}
+	m.EXPECT().EnableLog(true)
+	m.EXPECT().IsEnabled()
 
-func (t *LoggerTester) LogPolicy(event int, line string, pPolicyFormat []string, gPolicyFormat []string, pPolicy *[]interface{}, gPolicy *[]interface{}) {
-	t.event = event
-	t.line = ""
-	t.lastMessage = []interface{}{pPolicyFormat, gPolicyFormat, pPolicy, gPolicy}
-}
+	logger.EnableLog(true)
+	logger.IsEnabled()
 
-func TestLog(t *testing.T) {
-	lt := &LoggerTester{}
-	SetLogger(lt)
+	policy := map[string][][]string{}
+	m.EXPECT().LogPolicy(policy)
+	LogPolicy(policy)
+
+	var model [][]string
+	m.EXPECT().LogModel(model)
+	LogModel(model)
 
-	LogEnforce(1, "2", &[]interface{}{"3"}, &[]string{"4"}, &[]interface{}{true})
-	if lt.event != 1 || lt.line != "" || !reflect.DeepEqual(lt.lastMessage, []interface{}{"3"}) {
-		t.Errorf("incorrect logger message: %+v", lt.lastMessage)
-	}
+	matcher := "my_matcher"
+	request := []interface{}{"bob"}
+	result := true
+	var explains [][]string
+	m.EXPECT().LogEnforce(matcher, request, result, explains)
+	LogEnforce(matcher, request, result, explains)
 
-	LogModel(1, []string{"2"}, [][]string{{"3", "4"}})
-	if lt.event != 1 || lt.line != "" || !reflect.DeepEqual(lt.lastMessage, []interface{}{[][]string{{"3", "4"}}}) {
-		t.Errorf("incorrect logger message: %+v", lt.lastMessage)
-	}
+	var roles []string
+	m.EXPECT().LogRole(roles)
+	LogRole(roles)
 }
diff --git a/management_api_test.go b/management_api_test.go
index f5cb0d2..378fb06 100644
--- a/management_api_test.go
+++ b/management_api_test.go
@@ -253,4 +253,13 @@ func TestModifyGroupingPolicyAPI(t *testing.T) {
 	testGetUsers(t, e, []string{}, "data1_admin")
 	testGetUsers(t, e, []string{}, "data2_admin")
 	testGetUsers(t, e, []string{"eve"}, "data3_admin")
+	_, _ = e.AddGroupingPolicy("data3_admin", "data4_admin")
+	_, _ = e.UpdateGroupingPolicy([]string{"eve", "data3_admin"}, []string{"eve", "admin"})
+	_, _ = e.UpdateGroupingPolicy([]string{"data3_admin", "data4_admin"}, []string{"admin", "data4_admin"})
+	testGetUsers(t, e, []string{"admin"}, "data4_admin")
+	testGetUsers(t, e, []string{"eve"}, "admin")
+
+	testGetRoles(t, e, []string{"admin"}, "eve")
+	testGetRoles(t, e, []string{"data4_admin"}, "admin")
+
 }
diff --git a/model_test.go b/model_test.go
index 46b85ee..6dba41a 100644
--- a/model_test.go
+++ b/model_test.go
@@ -17,6 +17,7 @@ package casbin
 import (
 	"testing"
 
+	"github.com/casbin/casbin/v2/log"
 	fileadapter "github.com/casbin/casbin/v2/persist/file-adapter"
 	"github.com/casbin/casbin/v2/rbac"
 	defaultrolemanager "github.com/casbin/casbin/v2/rbac/default-role-manager"
@@ -348,6 +349,7 @@ func (rm *testCustomRoleManager) GetUsers(name string, domain ...string) ([]stri
 	return []string{}, nil
 }
 func (rm *testCustomRoleManager) PrintRoles() error { return nil }
+func (rm *testCustomRoleManager) SetLogger(logger log.Logger) {}
 
 func TestRBACModelWithCustomRoleManager(t *testing.T) {
 	e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")